发布v2.3.5版本,本次为优化版本。更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md

This commit is contained in:
孟帅 2023-02-26 14:18:22 +08:00
parent 34c373c11e
commit ab912d0ba6
111 changed files with 3068 additions and 9329 deletions

View File

@ -2,7 +2,7 @@
<div align="center">
<img width="140px" src="https://bufanyun.cn-bj.ufileos.com/hotgo/logo.sig.png">
<p>
<h1>HotGo V2.1</h1>
<h1>HotGo V2</h1>
</p>
<p align="center">
<a href="https://goframe.org/pages/viewpage.action?pageId=1114119" target="_blank">
@ -29,7 +29,15 @@
## 平台简介
* 基于全新Go Frame 2+Vue3+Naive UI+UinApp开发的全栖框架为二次开发而生适合中小型完整应用开发。
* 前端采用naive-ui-admin 、Vue、Naive UI、UinApp。
* 前端采用Naive-Ui-Admin、Vue、Naive UI、UinApp。
## 演示地址
- [https://hotgo.facms.cn/admin](https://hotgo.facms.cn/admin)
> 账号admin 密码123456
### 使用文档
[安装文档](docs/guide-zh-CN/start-installation.md) · [本地文档](docs/guide-zh-CN/README.md) · [更新历史](docs/guide-zh-CN/start-update-log.md) · [常见问题](docs/guide-zh-CN/start-issue.md)
## 特征
@ -43,7 +51,6 @@
## 后台内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、岗位),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
@ -68,13 +75,6 @@
> HotGo开源以来得到了大家的很多支持本项目初衷只为互相学习交流没有任何盈利性目的欢迎为HotGo贡献代码或提供建议
## 演示地址
- [https://hotgo.facms.cn/admin](https://hotgo.facms.cn/admin)
> 账号admin 密码123456
### 使用文档
[安装文档](docs/guide-zh-CN/start-installation.md) · [本地文档](docs/guide-zh-CN/README.md) · [更新历史](docs/guide-zh-CN/start-update-log.md) · [常见问题](docs/guide-zh-CN/start-issue.md)
## 演示图

View File

@ -5,7 +5,7 @@
- [系统介绍](../../README.md)
- [环境搭建](start-environment.md)
- [系统安装](start-installation.md)
- 生产部署
- [生产部署](start-deploy.md)
- [如何提问](start-questions.md)
- [常见问题](start-issue.md)
- [更新历史](start-update-log.md)
@ -14,11 +14,12 @@
#### 系统开发
- [目录结构](sys-catalog.md)
- 开发规范
- [开发规范](sys-exploit.md)
- [控制台](sys-console.md)
- 请求中间件和WebHook
- 权限控制
- 代码生成
- [权限控制](sys-auth.md)
- [数据库](sys-db.md)
- [代码生成](sys-code.md)
- 定时任务
- [消息队列](sys-queue.md)
- [功能扩展库](sys-library.md)
@ -40,11 +41,10 @@
### 前端开发
- 表单组件
- 权限
- [表单组件](web-form.md)
- Websocket客户端
- 工具库
- 发布部署
- [独立部署](web-deploy.md)
#### 附录
- [网址收录](append-website.md)

View File

@ -14,10 +14,21 @@
1、HotGo 后台进入 开发工具->插件管理->找到创建新插件,根据引导进行创建即可。
> 创建成功后会在 根目录的 addons 目录下生成插件文件
```
创建成功后默认情况下会在以下目录中生成插件文件假设新生成的插件名为hgexample
1. /server/addons/hgexample/ # 插件模块目录
2. /server/addons/modules/hgexample.go # 隐式注册插件文件
3. /server/resource/template/addons/hgexample # pc模板目录
4. /web/src/api/addons/hgexample # webApi目录
5. /web/src/views/addons/hgexample # web页面目录
# 默认情况下没有为web页面生成菜单权限因为在实际场景中插件不一定需要用到web页面所以如有需要请手动到后台 权限管理 -> 菜单权限->自行添加菜单和配置权限
```
2、创建插件完毕重启服务端后插件管理中会出现你新创建的插件信息。操作栏有几个按钮在此进行说明
- 安装:会自动执行 server/xxx插件/main.go 文件中的Install方法方法中的具体逻辑默认为空可以根据实际情况自行配置。如生成后台菜单、生成插件配置表初始化数据、迁移home页面、web项目文件等。
- 安装:会自动执行 server/hgexample/main.go 文件中的Install方法方法中的具体逻辑默认为空可以根据实际情况自行配置。如生成后台菜单、生成插件配置表初始化数据、迁移home页面、web项目文件等。
```
// Install 安装模块
func (m *module) Install(ctx context.Context) (err error) {
@ -26,7 +37,7 @@ func (m *module) Install(ctx context.Context) (err error) {
}
```
- 更新:会自动执行 server/xxx插件/main.go 文件中的Upgrade方法方法中的具体逻辑默认为空可以根据实际情况自行配置。
- 更新:会自动执行 server/hgexample/main.go 文件中的Upgrade方法方法中的具体逻辑默认为空可以根据实际情况自行配置。
```
// Upgrade 更新模块
func (m *module) Upgrade(ctx context.Context) (err error) {
@ -35,7 +46,7 @@ func (m *module) Upgrade(ctx context.Context) (err error) {
}
```
- 卸载:会自动执行 server/xxx插件/main.go 文件中的UnInstall方法方法中的具体逻辑默认为空可以根据实际情况自行配置。如会清除所有的数据表和已安装的信息等。
- 卸载:会自动执行 server/hgexample/main.go 文件中的UnInstall方法方法中的具体逻辑默认为空可以根据实际情况自行配置。如会清除所有的数据表和已安装的信息等。
```
// UnInstall 卸载模块
func (m *module) UnInstall(ctx context.Context) (err error) {
@ -57,7 +68,7 @@ func (m *module) UnInstall(ctx context.Context) (err error) {
一个简单的例子:
> 假设hgexample插件模块要通过主模块的服务接口更新插件配置
文件\server\addons\hgexample\model\input\sysin\config.go
插件模块input\server\addons\hgexample\model\input\sysin\config.go
```go
package sysin

View File

@ -8,6 +8,7 @@
#### 模块结构
- 文件路径server/internal/library/addons/module.go
```go
// Skeleton 模块骨架
type Skeleton struct {
@ -74,7 +75,7 @@ func test(ctx context.Context) {
}
```
- 更多辅助方法请参考\server\internal\library\addons
- 更多辅助方法请参考插件功能库server/internal/library/addons
#### 插件路由规则
- 如果你不喜欢现在的路由风格,可以自行调整。修改位置在:\server\internal\library\addons\addons.go的RouterPrefix方法。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,94 @@
## 生产部署
目录
- 编译配置
- 编译
- 修改生产配置文件
- 启动服务
- Nginx配置
### 编译配置
- 配置文件server/hack/config.yaml以下是默认配置
```yaml
gfcli:
build:
name: "hotgo" # 编译后的可执行文件名称
# arch: "all" #不填默认当前系统架构可选386,amd64,arm,all
# system: "all" #不填默认当前系统平台可选linux,darwin,windows,all
mod: "none"
cgo: 0
packSrc: "resource" # 将resource目录打包进可执行文件静态资源无需单独部署
packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径一般使用相对路径指定到本项目目录中
version: ""
output: "./temp/hotgo" # 可执行文件生成路径
extra: ""
```
### 编译
- 以下方式任选其一即可
1、 make一键编译 linux或mac环境推荐
```shell
cd server && make build
```
2、 按步骤手动编译
```shell
cd server # 切换到服务端目录下
rm -rf ./resource/public/admin/ # 删除之前的web资源
mkdir ./resource/public/admin/ # 重新创建web资源存放目录除首次编译后续可以跳过执行此步骤
cd ../web && yarn build # 切换到web项目下编译web项目
\cp -rf ./dist/* ../server/resource/public/admin/ # 将编译好的web资源复制到server对应的资源存放路径下
echo "y" | gf build # 编译hotgo服务端
# 不出意外你已经编译好了hotgo可执行文件
```
3、分服务编译
待写。
### 修改生产配置文件
- 配置文件server/manifest/config/config.yaml
> 如关闭代码生成功能、修改数据库地址、缓存驱动、队列驱动、日志路径等
### 启动服务
> 这里推可以接使用gf官方推荐的启动方式请参考https://goframe.org/pages/viewpage.action?pageId=1114403
### Nginx配置
```
# websocket
location ^~ /socket {
proxy_pass http://127.0.0.1:8000/socket;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_connect_timeout 600s;
proxy_read_timeout 600;
proxy_send_timeout 600s;
}
# http
location ^~ / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:8000/; # 设置代理服务器的协议和地址
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
}
```

View File

@ -46,11 +46,6 @@ database:
* /hotgo/web/.env.production
* /hotgo/web/.env
其中必改配置
```
VITE_PROXY=[["/admin","http://你的IP:8000/admin"]]
```
三、 启动服务

View File

@ -11,6 +11,14 @@
> 如果升级(覆盖)代码后打开会出现 sql 报错, 请检查更新的数据库格式或自行调整
### v2.3.5
updated 2023.02.26
- 优化: 调整消息队列消费初始化方式,支持在插件模块下注册消费者
- 修复:后台用户添加模态框无法正常显示问题
- 优化创建新插件生成增加web页面的生成
- 增加:增加生产部署、开发规范、生成代码、表单组件等相关文档
- 修复修复后台已知的一些小bug
### v2.2.10
updated 2023.02.23

View File

@ -0,0 +1,155 @@
## 权限控制
目录
- 一、菜单权限
- 二、数据权限
### 一、菜单权限
> 菜单权限可以控制web后台菜单功能每个菜单可以绑定一个或多个API权限API权限决定了用户是否可以调用服务端接口。
#### 为角色添加菜单权限
1、HotGo后台 -> 权限管理 -> 菜单权限 -> 找到添加菜单,按照表单提示添加你的菜单信息。
2、添加菜单完成后到 权限管理 -> 角色权限 -> 找到一位已有角色或者添加新角色 -> 在列表右侧操作栏找到菜单权限打开 -> 将第1步中添加的菜单分配给该角色即可。
#### 细粒度权限
> 一般用于web后台页面上的功能按钮对不同的角色展示不同的按钮时会用到。
1、参考【为角色添加菜单权限】添加一个按钮类型的菜单。并为菜单分配API权限假设你分配权限是/test/index
2、在web页面中创建一个按钮如下
```vue
<template>
<div>
<n-button v-if="hasPermission(['/test/index'])">拥有[/test/index]权限可见</n-button>
</div>
</template>
<script lang="ts" setup>
import { usePermission } from '@/hooks/web/usePermission';
const { hasPermission } = usePermission();
</script>
```
#### 菜单表主要字段解释
- 代码片段:[server/internal/model/input/adminin/menu.go](../../server/internal/model/input/adminin/menu.go)
```go
type MenuRouteMeta struct {
// 解释参考https://naive-ui-admin-docs.vercel.app/guide/router.html#%E5%A4%9A%E7%BA%A7%E8%B7%AF%E7%94%B1
Title string `json:"title"` // 菜单名称 一般必填
//Disabled bool `json:"disabled,omitempty"` // 禁用菜单
Icon string `json:"icon,omitempty"` // 菜单图标
KeepAlive bool `json:"keepAlive,omitempty"` // 缓存该路由
Hidden bool `json:"hidden,omitempty"` // 隐藏菜单
Sort int `json:"sort,omitempty"` // 排序越小越排前
AlwaysShow bool `json:"alwaysShow,omitempty"` // 取消自动计算根路由模式
ActiveMenu string `json:"activeMenu,omitempty"` // 当路由设置了该属性,则会高亮相对应的侧边栏。
// 这在某些场景非常有用,比如:一个列表页路由为:/list/basic-list
// 点击进入详情页,这时候路由为/list/basic-info/1但你想在侧边栏高亮列表的路由就可以进行如下设置
// 注意是配置高亮路由 `name`不是path
IsRoot bool `json:"isRoot,omitempty"` // 是否跟路由 顶部混合菜单,必须传 true否则左侧会显示异常场景就是分割菜单之后当一级菜单没有子菜单
FrameSrc string `json:"frameSrc,omitempty" ` // 内联外部地址
Permissions string `json:"permissions,omitempty"` // 菜单包含权限集合,满足其中一个就会显示
Affix bool `json:"affix,omitempty"` // 是否固定 设置为 true 之后 多页签不可删除
Type int `json:"type"` // 菜单类型
}
```
#### API权限验证流程
```mermaid
graph TD
A(用户请求API) --> B(验证中间件<br> server/internal/logic/middleware/admin_auth.go)
B -->|没有登录,但API需要登录| H(提示登录)
B -->|没有登录,但API不需要登录| G(验证通过,进入业务)
B -->|已登录| C(检查用户角色是否拥有API权限<br> server/internal/logic/admin/role.go,方法:Verify) -->|有权限| G(验证通过,进入业务)
C -->|API无需验证权限| G(验证通过,进入业务流程)
C -->|无权限| D(提示无权限)
```
#### 菜单权限添加或修改后多久生效?
- API权限实时生效web后台菜单刷新页面后生效无需重启服务
### 二、数据权限
> 数据权限是某人只能看到某些数据,可以为某人设置一定的数据范围,让其只能看到允许他看到的数据。
> 例如:公司存在多个销售部门,不同的部门仅能查看到自己所属部门下的数据;再如:用户有多级代理商,只能查看自己和自己下级的数据。
#### 目前已经支持的数据权限如下
| 数据范围 | 描述 |
|---------|--------------------------------------------|
| 全部权限 | 不做过滤,可以查看所有数据 |
| 当前部门 | 仅可以看到所属部门下的数据 |
| 当前以及下部门 | 仅可以看到所属以及下级部门的数据 |
| 自定义部门 | 可以看到设置的指定部门,支持多个 |
| 仅自己 | 仅可以看到自己的数据 |
| 自己和直属下级 | 仅可以看到自己和直属下级的数据直属下级是指当前用户往下1级的用户 |
| 自己和全部下级 | 仅可以看到自己和全部下级的数据,全部下级是指当前用户所有的下级用户,包含其下级的下级 |
#### 如何区分部门和下级用户?
- 在实际使用时,部门更多的是在公司或机构中使用,可以通过在 组织管理 -> 后台用户 ->为用户绑定部门
- 下级用户在代理商或分销系统中比较常见,后台用户由谁添加的,那么被添加的用户就是其下级用户。后续也将开放邀请码绑定下级功能。
#### 如何判断数据是谁的?
- 在数据库建表时,只要表中包含字段:`created_by`或`member_id`即可过滤 [仅自己、自己和直属下级、自己和全部下级] 类型的数据权限
#### 如何在具体业务中使用数据权限过滤?
- 只需在查询、更新或删除等ORM链式操作中加入相应`Handler`方法即可
- 如果你想对Handler有更多详细了解请查看https://goframe.org/pages/viewpage.action?pageId=20087340
- 下面提供一个简单使用例子:
```go
package main
import (
"context"
"hotgo/internal/dao"
"hotgo/internal/library/hgorm/handler"
)
func test(ctx context.Context) {
dao.AdminPost.Ctx(ctx).Where("id", 1).Handler(handler.FilterAuth).Scan(&res)
}
```
- 如果表中没有字段:`created_by`或`member_id`,也可以使用自定义字段方法来过滤数据权限,如下:
```go
package main
import (
"context"
"hotgo/internal/dao"
"hotgo/internal/library/hgorm/handler"
)
func test(ctx context.Context) {
dao.AdminPost.Ctx(ctx).Where("id", 1).Handler(handler.FilterAuthWithField("test_field")).Scan(&res)
}
```
- 过滤方法实现请参考:[server/internal/library/hgorm/handler/filter_auth.go](../../server/internal/library/hgorm/handler/filter_auth.go)

View File

@ -71,7 +71,7 @@
| --- --- global | 项目内主要的全局变量和系统的一些初始化操作 |
| --- --- logic | 业务逻辑封装管理,特定的业务逻辑实现和封装往往是项目中最复杂的部分 |
| --- --- model | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义 |
| --- --- --- input | 对内接口。用于controller调用service或service之间调用时的输入/输出结构定义输入过滤和预处理 |
| --- --- --- input | 对内接口。用于controller调用service或service之间调用时的输入/输出结构定义,以及输入过滤和预处理 |
| --- --- router | 注册对外接口和分组中间件 |
| --- --- service | 用于业务模块解耦的接口定义层具体的接口实现在logic中进行注入 |
| --- main.go | 插件始化文件和模块插拔接口 |
@ -93,7 +93,7 @@
| --- model | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义 |
| --- --- do | 用于dao数据操作中业务模型与实例模型转换由工具维护用户不能修改 |
| --- --- entity | 与数据集合绑定的程序数据结构定义,通常和数据表一一对应 |
| --- --- input | 对内接口。用于controller调用service或service之间调用时的输入/输出结构定义输入过滤和预处理 |
| --- --- input | 对内接口。用于controller调用service或service之间调用时的输入/输出结构定义,以及输入过滤和预处理 |
| --- packed | 将静态资源打包进可执行文件,无需单独部署 |
| --- queues | 为项目内所有的消息队列的消费者提供统一的初始化和处理 |
| --- router | 注册对外接口和分组中间件 |

View File

@ -0,0 +1,235 @@
## 代码生成
目录
- 使用条件
- 生成配置
- 一个生成增删改查列表例子
- 内置gf-cli
- 自定义生成模板
> 在HotGo中可以通过后台开发工具快速的一键生成CRUD自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。
### 使用条件
- hotgo 版本号 >= 2.2.10
- 使用前必须先看 [数据库](sys-db.md)
#### 增删改查列表
- 表自增长字段为 `id`
#### 关系树列表
- 表自增长字段为 `id`
### 生成配置
- 注意线上环境务必将allowedIPs参数设为空考虑到项目安全问题请勿线上生成使用
```yaml
hggen:
allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有只有允许的IP后台才能使用生成代码功能
selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库
disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到
delimiters: ["@{", "}"] # 模板引擎变量分隔符号
# 生成应用模型所有生成模板允许自定义可以参考default模板进行改造
application:
# CRUD模板
crud:
templates:
# 默认的主包模板
- group: "default" # 分组名称
isAddon: false # 是否为插件模板 falsetrue
masterPackage: "sys" # 主包名称需和controllerPath、logicPath、inputPath保持关联
templatePath: "./resource/generate/default/curd" # 模板路径
apiPath: "./api/admin" # gfApi生成路径
controllerPath: "./internal/controller/admin/sys" # 控制器生成路径
logicPath : "./internal/logic/sys" # 主要业务生成路径
inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径
routerPath : "./internal/router/genrouter" # 生成路由表路径
sqlPath : "./storage/data/generate" # 生成sql语句路径
webApiPath: "../web/src/api" # webApi生成路径
webViewsPath : "../web/src/views" # web页面生成路径
# 默认的插件包模板
- group: "addon" # 分组名称
isAddon: true # 是否为插件模板 falsetrue
masterPackage: "sys" # 主包名称需和controllerPath、logicPath、inputPath保持关联
templatePath: "./resource/generate/default/curd" # 模板路径
apiPath: "./addons/{$name}/api/admin" # gfApi生成路径
controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径
logicPath : "./addons/{$name}/logic/sys" # 主要业务生成路径
inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径
routerPath : "./addons/{$name}/router/genrouter" # 生成路由表路径
sqlPath : "./storage/data/generate/addons" # 生成sql语句路径
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
webViewsPath : "../web/src/views/addons/{$name}" # web页面生成路径
# 关系树列表模板
tree:
templates:
- group: "default"
templatePath: "./resource/generate/default/tree"
# 消息队列模板
queue:
templates:
- group: "default"
templatePath: "./resource/generate/default/queue"
# 定时任务模板
cron:
templates:
- group: "default"
templatePath: "./resource/generate/default/cron"
```
### 一个生成增删改查列表例子
- 推荐使用热编译方式启动HotGo这样生成完成页面自动刷新即可看到新生成内容无需手动重启
- 服务端热编译启动:`gf run main.go`, web前端启动`yarn dev`
1、创建数据表
1.1 创建测试表格表`hg_test_table`
```mysql
CREATE TABLE `hg_test_table` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`category_id` bigint(20) NOT NULL COMMENT '分类ID',
`title` varchar(64) NOT NULL COMMENT '标题',
`description` varchar(255) NOT NULL COMMENT '描述',
`content` text NOT NULL COMMENT '内容',
`image` varchar(255) DEFAULT NULL COMMENT '单图',
`attachfile` varchar(255) DEFAULT NULL COMMENT '附件',
`city_id` bigint(20) DEFAULT '0' COMMENT '所在城市',
`switch` int(11) DEFAULT '1' COMMENT '显示开关',
`sort` int(11) NOT NULL COMMENT '排序',
`status` tinyint(1) DEFAULT '1' COMMENT '状态',
`created_by` bigint(20) DEFAULT '0' COMMENT '创建者',
`updated_by` bigint(20) DEFAULT '0' COMMENT '更新者',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试表格';
```
1.2 测试分类表`hg_test_category`:
- 注意:该表为了方便功能演示已经内置无需再次创建,此处提示表结构只是为了方便大家梳理关联表关系
```mysql
CREATE TABLE `hg_test_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`name` varchar(255) NOT NULL COMMENT '分类名称',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`sort` int(11) NOT NULL COMMENT '排序',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`status` tinyint(1) DEFAULT '1' COMMENT '状态',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='测试分类';
```
1.3 插入测试数据
```mysql
INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `content`, `image`, `attachfile`, `city_id`, `switch`, `sort`, `status`, `created_by`, `updated_by`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 1, '测试标题', '描述', '<h2><strong>不知道写点啥!</strong></h2><p><br></p><iframe class=\"ql-video\" frameborder=\"0\" allowfullscreen=\"true\" src=\"https://media.w3.org/2010/05/sintel/trailer.mp4\"></iframe><p><br></p><p><img src=\"http://bufanyun.cn-bj.ufileos.com/hotgo/attachment/2023-02-09/cqdq9iuv0phsg8patk.png\"></p>', 'https://bufanyun.cn-bj.ufileos.com/hotgo/logo.sig.png', 'http://bufanyun.cn-bj.ufileos.com/hotgo/attachment/2022-12-30/cpf1x44idoycrtajf2.xlsx', 110102, 1, 10, 1, 0, 1, '2022-12-15 19:30:14', '2023-02-23 13:55:32', NULL);
```
2、创建生成配置
- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数:
![生成添加演示图](images/sys-code-add.png)
3、自定义配置
- 确认配置无误后,点击生成配置会自动跳转到生成配置页面,如下:
![生成配置页面](images/sys-code-config-init.png)
- 你可以在该页面点击`预览代码`查看生成的内容,如果无需调整亦可以直接点击`提交生成`,以下是预览代码效果:
![生成预览](images/sys-code-preview.png)
- 如果你对默认的生成配置不满意,可以根据页面表单提示,自定义表格属性和字段属性
- 这里假设我们要一个关联表,让表`hg_test_table`字段`category_id`去关联 表`hg_test_category`字段`id`,我们可以这样做:
![生成关联配置](images/sys-code-config-join.png)
- 如果你想调整主表每列字段的显示方式,可以点击上方导航栏中的 [主表字段] 进行调整
- 这里我们不做任何调整直接进入下一步。目的是为了后续演示对最终生成结果不满意的再次调整方案
![生成关联配置](images/sys-code-master.png)
4、以上内容都已配置无误后点击提交生成即可。
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
> 注意热编译自动刷新时考虑到实际开发环境电脑配置不同web端可能会优先于服务端重启加载完成此时会出现新生成菜单没有加载出来或某接口请求超时问题这是服务端正在启动中导致的一般稍等几秒再试即可。
- 接下来让我们看看生成的表格页面,效果如下:
![生成测试表格页面](images/sys-code-list.png)
5、假设我们对生成结果不满意有新的优化需求如下
1. 把`单图`、`附件`改换成上传组件
2. 把`创建者`、`更新者`隐藏
3. 把`分类ID`隐藏,然后把关联表中的`分类名称`显示出来
> 那么我们可以回到 开发工具 -> 代码生成 -> 找到刚刚生成的记录 -> 在右侧操作栏中点击生成配置,再次进入配置页面做如下操作即可:
1. 点击上方导航栏[主表字段],调整字段选项:
![修改主表配置](images/sys-code-master-save.png)
2. 点击上方导航栏[关联表],调整字段选项:
![修改关联表配置](images/sys-code-config-join-save.png)
3. 返回[基本信息],勾选强制覆盖,然后点击`预览代码`,确认生成代码无误后点击`提交生成`
![强制覆盖并提交生成](images/sys-code-config-post.png)
- 生成完成刷新页面后,再次来到`测试表格`,发现表格已经调整到了我们预期效果
1. 列表效果:
![最终列表效果](images/sys-code-list-ok.png)
2. 编辑表单效果
![最终编辑表单效果](images/sys-code-list-edit-ok.png)
- 至此生成增删改查列表示例结束!
### 内置gf-cli
> 由于gf版本更新较常出现向下不兼容的情况所以我们为了保证生成代码的依赖稳定性我们将gf-cli工具内置到了系统中并做了一些在线执行的调整。
- 我们会定期更新和gf最新版本生成功能保持一致这样不论是你通过gf命令还是通过后台生成的代码格式都是一样的遵循相同的代码规范和开发方式
- 后续我们也将开放在线运行`gf gen dao`、`gf gen service`功能。在做插件开发时也会支持到在线生成插件下的service接口这将会使得插件开发更加方便
### 自定义生成模板
> 系统内置了两组CURD生成模板请参考[生成配置]。default是默认的生成到主模块下addon是默认生成到指定的插件下
- 如果你在实际的开发过程中默认模板需要调整的地方较多时HotGo允许你新建新的模板分组来满足你的需求。新的模板可根据现有模板基础拷贝一份出来做改造默认模板目录[server/resource/generate/default](../../server/resource/generate/default)

113
docs/guide-zh-CN/sys-db.md Normal file
View File

@ -0,0 +1,113 @@
## 数据库
目录
- 字段类型
- 特殊字段默认表单组件
- 特殊字段默认表单验证器
- SQL默认查询方式
- 其他默认选项
- 常见问题
### 字段类型
- 创建数据库表当按如下的规则进行字段命名、类型、属性设置和备注后再生成CRUD代码时会自动生成对应的Api、控制器、业务逻辑、Web页面、[表单组件](web-form.md)等的一些默认属性
- 当你了解这些默认技巧后,会有效提高你在实际开发中的生产效率
| 数据库类型 | 额外属性 | 转换Go类型 | 转换Ts类型 | 表单组件 |
|---------------------------------------------------------------|--------------|--------------|---------|-----------------------|
| int, tinyint,small_int,smallint,medium_int,mediumint,serial | / | int | number | InputNumber(数字输入框) |
| int, tinyint,small_int,smallint,medium_int,mediumint,serial | unsigned | uint | number | InputNumber(数字输入框) |
| big_int,bigint,bigserial | / | int64 | number | InputNumber(数字输入框) |
| big_int,bigint,bigserial | unsigned | uint64 | number | InputNumber(数字输入框) |
| real | / | float32 | number | InputNumber(数字输入框) |
| float,double,decimal,money,numeric,smallmoney | / | float64 | number | InputNumber(数字输入框) |
| bit(1) 、bit(true)、bit(false) | / | bool | boolean | Input(文本输入框,默认) |
| bit | / | int64-bytes | array | InputDynamic(动态KV表单) |
| bit | unsigned | uint64-bytes | array | InputDynamic (动态KV表单) |
| bool | / | bool | boolean | Input(文本输入框,默认) |
| date | / | *gtime.Time | string | Date(日期选择器) |
| datetime,timestamp,timestamptz | / | *gtime.Time | string | Time(时间选择器) |
| json | / | *gjson.Json | string | Input(文本输入框) |
| jsonb | / | *gjson.Json | string | Input(文本输入框) |
| 以下为物理类型中包含字段部分时的转换方式,默认情况 | / | / | / | / |
| text,char,character | / | string | string | Input(文本输入框) |
| float,double,numeric | / | string | string | Input(文本输入框) |
| bool | / | bool | boolean | Input(文本输入框,默认) |
| binary,blob | / | []byte | string | Input(文本输入框,默认) |
| int | / | int | number | InputNumber(数字输入框) |
| int | unsigned | int | number | InputNumber(数字输入框) |
| time | / | *gtime.Time | string | Time(时间选择器) |
| date | / | *gtime.Time | string | Date(日期选择器) |
| 没有满足以上任何条件的 | / | string | string | Input(文本输入框) |
### 特殊字段默认表单组件
- 以下字段在不设置表单组件时会默认使用的表单组件
| 数据库字段 | 字段名称 | 表单组件 |
|--------------|----------------------|----------------------|
| status | 状态字段任意int类型 | Select (单选下拉框) |
| created_at | 创建时间字段 | TimeRange (时间范围选择) |
| province_id | 省份字段任意int类型 | CitySelector (省市区选择) |
| city_id | 省份字段任意int类型 | CitySelector (省市区选择) |
| 任意字串符字段 | 长度>= 200 and <= 500 | InputTextarea (文本域) |
| 任意字串符字段 | 长度> 500 | InputEditor (富文本) |
### 特殊字段默认表单验证器
- 以下字段在不设置表单组件时会默认使用的表单验证器
| 数据库字段/Go类型 | 字段名称 | 表单验证规则 |
|-------------------|--------|-----------------------|
| mobile | 手机号 | 不为空时必须是手机号码(国内) |
| qq | QQ | 不为空时必须是QQ号码 |
| email | 邮箱地址 | 不为空时必须是邮箱格式 |
| id_card | 身份证号码 | 不为空时必须是15或18位身份证号码 |
| bank_card | 银行卡号码 | 银行卡号码 |
| password | 密码 | 密码验证必须包含6-18为字母和数字 |
| price | 价格 | 金额验证最多允许输入10位整数及2位小数 |
| Go类型为uint、uint64 | 正整数 | 非零正整数验证 |
### SQL默认查询方式
- Go类型取决于数据库物理类型请参考 [字段类型] 部分
| Go类型 | 查询方式 |
|-------------------------|--------------------------------------|
| string | LIKE |
| date,datetime | = |
| int,uint,int64,uint64 | = |
| []int,[]int64,[]uint64 | IN (...) |
| float32,float64 | = |
| []byte4 | =(默认) |
| time.Time,*gtime.Time | = |
| *gjson.Json | JSON_CONTAINS(json_doc, val[, path]) |
### 其他默认选项
#### 默认字典选项
- 数据库字段为 `status`且类型为任意数字类型的会使用系统默认的状态字典
#### 默认属性
- 默认必填,当数据库字段存在非空`IS_NULLABLE`属性时,默认勾选必填验证
- 默认唯一,当数据库字段属性存在`UNI`时,默认勾选唯一值验证
- 默认主键,当数据库字段属性存在`PRI`时,默认为主键,不允许编辑
- 默认最大排序,当数据库字段存在`sort`时,默认开启排序,添加表单自动获取最大排序增量值并填充表单
- 默认列名,默认使用字段注释作为表格的列名。当数据库字段未设置注释时,默认使用字段名称作为列名
#### 自动更新/插入
- 自动更新,当数据库字段为`updated_at`(更新时间),`updated_by`(更新者)
- 自动插入,当数据库字段为`created_at`(创建时间),`created_by`(创建者)
- 软删除,表存在字段`deleted_at`时使用表的Orm模型查询条件将会自动加入[ `deleted_at` IS NULL ],删除时只更新删除时间而不会真的删除数据
- 树表:不论更新插入,都会根据表中字段`pid`(上级ID)自动维护`level`(树等级)和`tree`(关系树)
> 这里只列举了较为常用的默认规则,其他更多默认规则请参考:[server/internal/library/hggen/views/column_default.go](../../server/internal/library/hggen/views/column_default.go)
#### 常见问题
待补充。

View File

@ -0,0 +1,197 @@
## 开发规范
目录
- 规范说明
- 规范建议
- 命名规范
- 编码规范
- 框架规范
- 插件规范
- 数据库规范
- 包功能规范
### 规范说明
- 开发规范意义是是让大家尽可能写出风格统一的代码,让团队代码有更好的可读性与可维护性。
- 在实际业务开发中,除了要提高业务开发效率,保证线上业务高性能外,好的编程习惯也是一个开发人员基本素养之一。
### 规范建议
- 在我们实际开发中,有很多开发人可能是由某一语言转到另外一个语言领域,在转到另外一门语言后, 我们都会保留着对旧语言的编程习惯,在这里,我建议的是,虽然不同语言之前的某些规范可能是相通的, 但是我们最好能够按照官方的一些demo来熟悉是渐渐适应当前语言的编程规范而不是直接将原来语言的编程规范也随之迁移过来。
### 命名规范
#### 命名准则
* 当变量名称在定义和最后一次使用之间的距离很短时,简短的名称看起来会更好
* 变量命名应尽量描述其内容,而不是类型
* 常量命名应尽量描述其值,而不是如何使用这个值
* 在遇到forif等循环或分支时推荐单个字母命名来标识参数和返回值
* package名称也是命名的一部分推荐全部使用小写请尽量将其利用起来
* 使用一致的命名风格
#### 文件命名规范
* 全部小写
* 除unit test外避免下划线(_)
* 文件名称不宜过长,但不必为了简短而忽略含义
#### 变量命名规范参考
* 驼峰命名
* 见名知义,避免拼音替代英文
* 不建议包含下划线(_)
* 不建议包含数字
#### 函数、常量命名规范
* 驼峰式命名
* 可导出的必须首字母大写
* 不可导出的必须首字母小写
* 避免全部大写与下划线(_)组合
#### 参考文档
* [Practical Go: Real world advice for writing maintainable Go programs](https://dave.cheney.net/practical-go/presentations/gophercon-singapore-2019.html#_simplicity)
### 编码规范
#### import
* 单行import不建议用圆括号包裹
* 按照`官方包`NEW LINE`当前工程包`NEW LINE`第三方依赖包`顺序引入
```go
import (
"context"
"string"
"greet/user/internal/config"
"google.golang.org/grpc"
)
```
#### 函数返回
* 对象避免非指针返回
* 遵循有正常值返回则一定无error有error则一定无正常值返回的原则
#### 错误处理
* 有error必须处理如果不能处理就必须抛出。
* 避免下划线(_)接收error
#### 函数体编码
* 建议一个block结束空一行如if、for等
```go
func main (){
if x==1{
// do something
}
fmt.println("xxx")
}
```
* return前尽可能空一行
```go
func getUser(id string)(string,error){
....
return "xx",nil
}
```
### 框架规范
- 项目启动初始化方法都放在`server/internal/global/init.go`,插件放在:`server/addons/xxx插件/global/init.go`
- Api 对外接口层 都放在`server/api`,根据实际应用和应用具体功能划分子目录,插件放在:`server/addons/xxx插件/api`
- Router 路由注册方式应尽可能按gf2.0版本风格定义参考https://goframe.org/pages/viewpage.action?pageId=30736904
- Controller 控制器层 都放在`server/internal/controller`,根据应用和具体功能拆分子目录,插件放在:`server/addons/xxx插件/controller`
- Input 对内接口层 都放在`server/internal/model/input`,根据应用和具体功能拆分子目录并以`in`结尾,插件放在:`server/addons/xxx插件/model/input`
- Logic 业务逻辑层 统一放在`server/internal/logic` 根据实际业务需求按logic/xxx/*.go拆分插件放在`server/addons/xxx插件/logic`
- Orm 模型实体层 统一放在`server/internal/model/entity`
- Dao 数据访问层 统一放在`server/internal/dao`
- 自定义工具函数都放在 `server/utility`,且应根据不同类型的方法做分包,相同类型的方法放在同一个包内
- 状态枚举统一调用`server/internal/consts`中的常量和属性
### 插件规范
- 插件命名统一全部小写
- 资源文件在 `addons/xxx插件/resource`或`server/resource`
- 每个插件之前应该是完全独立的,尽可能避免插件于插件之间的调用依赖
- 插件和主模块之前的调用应尽可能通过 Service 接口来调用
- 插件业务初始化顺序下应在主模块之后应尽可能避免使用init方法隐式初始化
### 数据库规范
#### 命名规范
- 数据表名小写,多关键字使用下划线分割(关键字尽量全称)
- 字段名小写,多关键字使用下划线分割(关键字尽量全称)
- 禁止使用保留字并且尽量少用含有关键词来命名
- 临时表必须以tmp_开头、以日期结尾备份表必须以bak_开头、以日期结尾
- 插件模块表名建议以`hg_addon_`开头,如:`hg_addon_hgexample_table`,含义:`插件_案例_表格`。在生成代码时可自动识别实体命名为:`Table`
#### 基础规范
- 所有的字段必须添加注释
- 尽可能地使用InnoDB作为表的存储引擎
- 所有的表和字段必须添加注释
- 尽量控制表行数在500万以内
- 尽可能采用冷热数据分离策略
- 禁止以图片、文件等二进制数据
#### 表设计规范
- 尽可能每张表的索引数量控制在5个以内
- 每一张InnoDB表都必须含有一个主键自增主键必须是`bigint`类型
- 尽可能避免使用外键约束
- 设置数据表架构应考虑后期扩展型
- 遵循范式与冗余平衡原则
#### 字段设计规范
- 尽可能将所有的数据列定义为 `NOT NULL` 类型
- 避免 ENUM 数据类型
- json 存储的数据用 `json`字段代替 `text`
- 表与表关联的键名保持一致或以关联表名的缩写为前缀
- 固定长度的字符串字段务必使用 `char`
- 使用 `UNSIGNEG` 存储非负整数
- 禁止敏感数据以明文形式存储
- 金额相关的数据必须使用 `DECIMAL` 数据类型
- 尽量所有表有 `status` 字段来标注数据状态(1:启用,2:禁用,3:已删除),业务状态请使用其他字段;`status`字段类型 为带符号的 `tinyint(1)`。如果还需要其他的数据状态 请先判断该状态的数据是有用的数据还是无意义的数据,有用的数据状态 > 0,无意义的数据状态 < -1
- 所有的删除(除开清空回收站操作) 请标记 `status` 为 3
- 创建时间字段为`created_at`,修改时间字段为`updated_at`,删除时间字段为`deleted_at`,类型`datetime`
- 用户关联 id 字段为 `member_id``created_by`
- 排序字段为 `sort`
- 区分应用字段为 `app_id`
- 区分插件来源需要增加字段为 `addon_name``is_addon`
- 关系树表中必须包含字段:`id`(主键)、`pid`(上级ID)、`level`(树等级)、`tree`(关系树)
- 多选、多图、多文件字段类型应尽量使用类型`json`
### 包功能规范
##### Input(对内接口 - 过滤验证)
- 接收Api 对外接口数据和参数过滤
- 对提交的复杂数据参数进行验证处理返回
##### View(视图)
- 数据处理显示
- 页面渲染
##### Controller(控制器)
- 数据接收
- 服务调用
- 简单CURD入库
- 简单业务逻辑
##### Logic(业务逻辑 - 服务接口功能)
- 数据逻辑处理
- 数据入库
- 数据查询

View File

@ -3,7 +3,7 @@
目录
- 缓存驱动
- 上下文(待写)
- 请求上下文(待写)
- JWT待写
- 地理定位(待写)
- 通知(待写)
@ -48,9 +48,42 @@ func test() {
```
### 上下文
### 请求上下文
- 主要用于在处理HTTP和websocket请求时通过中间件将用户、应用、插件等信息绑定到上下文中方便在做业务处理时用到这些信息
```go
// 待写
package admin
import (
"fmt"
"context"
"hotgo/internal/library/contexts"
"hotgo/internal/library/addons"
)
func test(ctx context.Context) {
// 获取当前请求的所有上下文变量
var ctxModel = contexts.Get(ctx)
fmt.Printf("当前请求的所有上下文变量:%+v\n", ctxModel)
// 获取当前请求的应用模块
var module = contexts.GetModule(ctx)
fmt.Printf("当前请求的应用:%+v\n", module)
// 获取当前请求的用户信息
var member = contexts.GetUser(ctx)
fmt.Printf("当前访问用户信息:%+v\n", member)
// 获取当前请求的插件模块
fmt.Printf("当前是否为插件请求:%v", contexts.IsAddonRequest(ctx))
if contexts.IsAddonRequest(ctx) {
fmt.Printf("当前插件名称:%v", contexts.GetAddonName(ctx))
fmt.Printf("当前插件信息:%v", addons.GetModule(contexts.GetAddonName(ctx)))
}
}
```
### JWT

View File

@ -44,7 +44,7 @@ queue:
### 一个例子
每个被发送到队列的消息应该被定义为一个文件结构。
每个被发送到队列的消息应该被定义为一个单独的文件结构。
例如,如果您需要异步记录系统日志,内容大致如下:
@ -63,7 +63,7 @@ import (
)
func init() {
jobList = append(jobList, SysLog)
queue.RegisterConsumer(SysLog)
}
// SysLog 系统日志
@ -71,13 +71,13 @@ var SysLog = &qSysLog{}
type qSysLog struct{}
// getTopic 主题
func (q *qSysLog) getTopic() string {
// GetTopic 主题
func (q *qSysLog) GetTopic() string {
return consts.QueueLogTopic
}
// handle 处理消息
func (q *qSysLog) handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
// Handle 处理消息
func (q *qSysLog) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
var data entity.SysLog
if err = json.Unmarshal(mqMsg.Body, &data); err != nil {
return err
@ -85,7 +85,6 @@ func (q *qSysLog) handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
return service.SysLog().RealWrite(ctx, data)
}
```
下面是将消息添加到队列的方式,大概内容如下:
@ -101,7 +100,9 @@ import (
)
func test() {
data := &entity.SysLog{}
data := &entity.SysLog{
//...
}
if err := queue.Push(consts.QueueLogTopic, data); err != nil {
fmt.Printf("queue.Push err:%+v", err)
}

View File

@ -0,0 +1,261 @@
## 独立部署
目录
- 构建
- 旧版浏览器兼容
- 预览
- 分析构建文件体积
- 压缩
- 部署
> 在实际开发中web前端也可能需要独立部署所以在此提供一下部署方案。
### 构建
项目开发完成之后,执行以下命令进行构建
```shell
yarn build
```
构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件
### 旧版浏览器兼容
在 .env.production 内
设置 `VITE_LEGACY=true` 即可打包出兼容旧版浏览器的代码
```shell
VITE_LEGACY = true
```
### 预览
发布之前可以在本地进行预览,有多种方式,这里介绍两种
##### 不能直接打开构建后的 html 文件
使用项目自定的命令进行预览(推荐)
```shell
# 先打包在进行预览
yarn preview
# 直接预览本地 dist 文件目录
yarn preview:dist
```
- 本地服务器预览(通过 live-server)
```shell
# 1.全局安装live-server
yarn global add live-server
# 2. 进入打包的后目录
cd ./dist
# 本地预览默认端口8080
live-server
# 指定端口
live-server --port 9000
```
### 分析构建文件体积
如果你的构建文件很大,可以通过项目内置 [rollup-plugin-analyzer](https://github.com/doesdev/rollup-plugin-analyzer) 插件进行代码体积分析,从而优化你的代码。
```shell
yarn report
```
运行之后,在自动打开的页面可以看到具体的体积分布,以分析哪些依赖有问题。
- 左上角可以切换 显示 gzip 或者 brotli
![start-deploy-report.png](./images/start-deploy-report.png)
### 压缩
#### 开启 gzip 压缩
开启 gzip并配合 nginx 的 `gzip_static` 功能可以大大加快页面访问速度
- 只需开启 `VITE_BUILD_COMPRESS='gzip'` 即可在打包的同时生成 .gz 文件
```shell
# 根据自己路径来配置更改
# 例如部署在nginx /next/路径下 则VITE_PUBLIC_PATH=/next/
VITE_PUBLIC_PATH=/
```
#### 开启 brotli 压缩
brotli 是比 gzip 压缩率更高的算法,可以与 gzip 共存不会冲突,需要 nginx 安装指定模块并开启即可。
- 只需开启 VITE_BUILD_COMPRESS='brotli' 即可在打包的同时生成 .br 文件
```shell
# 根据自己路径来配置更改
# 例如部署在nginx /next/路径下 则VITE_PUBLIC_PATH=/next/
VITE_PUBLIC_PATH=/
```
#### 同时开启 gzip 与 brotli
只需开启 VITE_BUILD_COMPRESS='brotli,gzip' 即可在打包的同时生成 .gz 和 .br 文件。
#### gzip 与 brotli 在 nginx 内的配置
```
http {
# 开启gzip
gzip on;
# 开启gzip_static
# gzip_static 开启后可能会报错,需要安装相应的模块, 具体安装方式可以自行查询
# 只有这个开启vue文件打包的.gz文件才会有效果否则不需要开启gzip进行打包
gzip_static on;
gzip_proxied any;
gzip_min_length 1k;
gzip_buffers 4 16k;
#如果nginx中使用了多层代理 必须设置这个才可以开启gzip。
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
# 开启 brotli压缩
# 需要安装对应的nginx模块,具体安装方式可以自行查询
# 可以与gzip共存不会冲突
brotli on;
brotli_comp_level 6;
brotli_buffers 16 8k;
brotli_min_length 20;
brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
}
```
### 部署
- 注意:项目默认是在生产环境开启 Mock这样做非常不好只是为了演示环境有数据不建议在生产环境使用 Mock而应该使用真实的后台接口并将 Mock 关闭。
#### 发布
简单的部署只需要将最终生成的静态文件dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可,需要注意的是其中的 index.html 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。
例如上传到 nginx `/srv/www/project/index.html`
```
# nginx配置
location / {
# 不缓存html防止程序更新后缓存继续生效
if ($request_filename ~* .*\.(?:htm|html)$) {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
access_log on;
}
# 这里是vue打包文件dist内的文件的存放路径
root /srv/www/project/;
index index.html index.htm;
}
```
部署时可能会发现资源路径不对,只需要修改`.env.production`文件即可。
```shell
# 根据自己路径来配置更改
# 注意需要以 / 开头和结尾
VITE_PUBLIC_PATH=/
VITE_PUBLIC_PATH=/xxx/
```
#### 前端路由与服务端的结合
项目前端路由使用的是 vue-router所以你可以选择两种方式history 和 hash。
- hash 默认会在 url 后面拼接#
- history 则不会,不过 history 需要服务器配合
可在 src/router/index.ts 内进行 mode 修改
```vue
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
createRouter({
history: createWebHashHistory(),
// or
history: createWebHistory(),
});
```
#### history 路由模式下服务端配置
开启 history 模式需要服务器配置,更多的服务器配置详情可以看 [history-mode](https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode)
这里以 nginx 配置为例
##### 部署到根目录
```
server {
listen 80;
location / {
# 用于配合 History 使用
try_files $uri $uri/ /index.html;
}
}
```
#### 部署到非根目录
1. 首先需要在打包的时候更改配置
```shell
# 在.env.production内配置子目录路径
VITE_PUBLIC_PATH = /sub/
```
```
server {
listen 80;
server_name localhost;
location /sub/ {
# 这里是vue打包文件dist内的文件的存放路径
alias /srv/www/project/;
index index.html index.htm;
try_files $uri $uri/ /sub/index.html;
}
}
```
#### 使用 nginx 处理跨域
使用 nginx 处理项目部署后的跨域问题
1. 配置前端项目接口地址
```
# 在.env.production内配置接口地址
VITE_GLOB_API_URL=/api
```
2. 在 nginx 配置请求转发到后台
```
server {
listen 8080;
server_name localhost;
# 接口代理,用于解决跨域问题
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 后台接口地址
proxy_pass http://110.110.1.1:8080/api;
proxy_redirect default;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}
}
```

View File

@ -0,0 +1,821 @@
## 表单组件
目录
- 文本输入 Input
- 数字输入 Input Number
- 文本域 InputTextarea
- 富文本 InputEditor
- 动态键值对 InputDynamic
- 日期选择 Date(Y-M-D)
- 日期范围选择 DateRange
- 时间选择 Time(Y-M-D H:i:s)
- 时间范围选择 TimeRange
- 单选按钮 Radio
- 复选框 Checkbox
- 单选下拉框 Select
- 多选下拉框 SelectMultiple
- 树型选择 Tree Select
- 单图上传 UploadImage
- 多图上传 UploadImage
- 单文件上传 UploadFile
- 多文件上传 UploadFile
- 开关 Switch
- 评分 Rate
- 省市区选择器 CitySelector
- 图标选择器 IconSelector
### 文本输入 Input
```vue
<template>
<n-input v-model:value="value" type="text" placeholder="基本的 Input" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 数字输入 Input Number
```vue
<template>
<n-input-number v-model:value="value" clearable />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 文本域 InputTextarea
```vue
<template>
<n-input
v-model:value="value"
type="textarea"
placeholder="基本的 Textarea"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 富文本 InputEditor
```vue
<template>
<Editor style="height: 450px" v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import Editor from '@/components/Editor/editor.vue';
const value = ref(null);
</script>
```
### 动态键值对 InputDynamic
```vue
<template>
<n-dynamic-input
v-model:value="value"
preset="pair"
key-placeholder="键名"
value-placeholder="键值"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 日期选择 Date(Y-M-D)
```vue
<template>
<DatePicker v-model:formValue="value" type="date" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import DatePicker from '@/components/DatePicker/datePicker.vue';
const value = ref(null);
</script>
```
### 日期范围选择 DateRange
```vue
<template>
<DatePicker
v-model:startValue="startValue"
v-model:endValue="endValue"
type="datetimerange"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import DatePicker from '@/components/DatePicker/datePicker.vue';
const startValue = ref(null);
const endValue = ref(null);
</script>
```
### 时间选择 Time(Y-M-D H:i:s)
```vue
<template>
<n-time-picker :default-formatted-value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 时间范围选择 TimeRange
```vue
<template>
<template>
<n-space>
<n-time-picker :default-value="startValue" />
<n-time-picker :default-value="endValue" />
</n-space>
</template>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const startValue = ref(null);
const endValue = ref(null);
</script>
```
### 单选按钮 Radio
```vue
<template>
<n-space vertical>
<n-radio-group v-model:value="value" name="radiobuttongroup1">
<n-radio-button
v-for="song in songs"
:key="song.value"
:value="song.value"
:disabled="
(song.label === 'Live Forever' && disabled1) ||
(song.label === 'Shakermaker' && disabled2)
"
:label="song.label"
/>
</n-radio-group>
<n-space>
<n-checkbox v-model:checked="disabled2" style="margin-right: 12px">
禁用 Shakemaker
</n-checkbox>
<n-checkbox v-model:checked="disabled1">
禁用 Live Forever
</n-checkbox>
</n-space>
</n-space>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
value: ref(null),
disabled2: ref(false),
disabled1: ref(false),
songs: [
{
value: "Rock'n'Roll Star",
label: "Rock'n'Roll Star"
},
{
value: 'Shakermaker',
label: 'Shakermaker'
},
{
value: 'Live Forever',
label: 'Live Forever'
},
{
value: 'Up in the Sky',
label: 'Up in the Sky'
},
{
value: '...',
label: '...'
}
].map((s) => {
s.value = s.value.toLowerCase()
return s
})
}
}
})
</script>
```
### 复选框 Checkbox
```vue
<template>
<n-space item-style="display: flex;" align="center">
<n-checkbox v-model:checked="value">
复选框
</n-checkbox>
<n-checkbox v-model:checked="value" />
<n-checkbox v-model:checked="value" :disabled="disabled">
复选框
</n-checkbox>
<n-button size="small" @click="disabled = !disabled">
禁用
</n-button>
</n-space>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
value: ref(false),
disabled: ref(true)
}
}
})
</script>
```
### 单选下拉框 Select
```vue
<template>
<n-space vertical>
<n-select v-model:value="value" :options="options" />
<n-select v-model:value="value" disabled :options="options" />
</n-space>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
value: ref(null),
options: [
{
label: "Everybody's Got Something to Hide Except Me and My Monkey",
value: 'song0',
disabled: true
},
{
label: 'Drive My Car',
value: 'song1'
},
{
label: 'Norwegian Wood',
value: 'song2'
},
{
label: "You Won't See",
value: 'song3',
disabled: true
},
{
label: 'Nowhere Man',
value: 'song4'
},
{
label: 'Think For Yourself',
value: 'song5'
},
{
label: 'The Word',
value: 'song6'
},
{
label: 'Michelle',
value: 'song7',
disabled: true
},
{
label: 'What goes on',
value: 'song8'
},
{
label: 'Girl',
value: 'song9'
},
{
label: "I'm looking through you",
value: 'song10'
},
{
label: 'In My Life',
value: 'song11'
},
{
label: 'Wait',
value: 'song12'
}
]
}
}
})
</script>
```
### 多选下拉框 SelectMultiple
```vue
<template>
<n-space vertical>
<n-select v-model:value="value" multiple :options="options" />
<n-select v-model:value="value" multiple disabled :options="options" />
</n-space>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
value: ref(['song3']),
options: [
{
label: "Everybody's Got Something to Hide Except Me and My Monkey",
value: 'song0',
disabled: true
},
{
label: 'Drive My Car',
value: 'song1'
},
{
label: 'Norwegian Wood',
value: 'song2'
},
{
label: "You Won't See",
value: 'song3',
disabled: true
},
{
label: 'Nowhere Man',
value: 'song4'
},
{
label: 'Think For Yourself',
value: 'song5'
},
{
label: 'The Word',
value: 'song6'
},
{
label: 'Michelle',
value: 'song7',
disabled: true
},
{
label: 'What goes on',
value: 'song8'
},
{
label: 'Girl',
value: 'song9'
},
{
label: "I'm looking through you",
value: 'song10'
},
{
label: 'In My Life',
value: 'song11'
},
{
label: 'Wait',
value: 'song12'
}
]
}
}
})
</script>
```
### 树型选择 Tree Select
```vue
<template>
<n-tree-select
:options="options"
default-value="Drive My Car"
@update:value="handleUpdateValue"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { TreeSelectOption } from 'naive-ui'
export default defineComponent({
setup () {
return {
handleUpdateValue (
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option)
},
options: [
{
label: 'Rubber Soul',
key: 'Rubber Soul',
children: [
{
label:
"Everybody's Got Something to Hide Except Me and My Monkey",
key: "Everybody's Got Something to Hide Except Me and My Monkey"
},
{
label: 'Drive My Car',
key: 'Drive My Car',
disabled: true
},
{
label: 'Norwegian Wood',
key: 'Norwegian Wood'
},
{
label: "You Won't See",
key: "You Won't See",
disabled: true
},
{
label: 'Nowhere Man',
key: 'Nowhere Man'
},
{
label: 'Think For Yourself',
key: 'Think For Yourself'
},
{
label: 'The Word',
key: 'The Word'
},
{
label: 'Michelle',
key: 'Michelle',
disabled: true
},
{
label: 'What goes on',
key: 'What goes on'
},
{
label: 'Girl',
key: 'Girl'
},
{
label: "I'm looking through you",
key: "I'm looking through you"
},
{
label: 'In My Life',
key: 'In My Life'
},
{
label: 'Wait',
key: 'Wait'
}
]
},
{
label: 'Let It Be',
key: 'Let It Be Album',
children: [
{
label: 'Two Of Us',
key: 'Two Of Us'
},
{
label: 'Dig A Pony',
key: 'Dig A Pony'
},
{
label: 'Across The Universe',
key: 'Across The Universe'
},
{
label: 'I Me Mine',
key: 'I Me Mine'
},
{
label: 'Dig It',
key: 'Dig It'
},
{
label: 'Let It Be',
key: 'Let It Be'
},
{
label: 'Maggie Mae',
key: 'Maggie Mae'
},
{
label: "I've Got A Feeling",
key: "I've Got A Feeling"
},
{
label: 'One After 909',
key: 'One After 909'
},
{
label: 'The Long And Winding Road',
key: 'The Long And Winding Road'
},
{
label: 'For You Blue',
key: 'For You Blue'
},
{
label: 'Get Back',
key: 'Get Back'
}
]
}
]
}
}
})
</script><template>
<n-tree-select
:options="options"
default-value="Drive My Car"
@update:value="handleUpdateValue"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { TreeSelectOption } from 'naive-ui'
export default defineComponent({
setup () {
return {
handleUpdateValue (
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option)
},
options: [
{
label: 'Rubber Soul',
key: 'Rubber Soul',
children: [
{
label:
"Everybody's Got Something to Hide Except Me and My Monkey",
key: "Everybody's Got Something to Hide Except Me and My Monkey"
},
{
label: 'Drive My Car',
key: 'Drive My Car',
disabled: true
},
{
label: 'Norwegian Wood',
key: 'Norwegian Wood'
},
{
label: "You Won't See",
key: "You Won't See",
disabled: true
},
{
label: 'Nowhere Man',
key: 'Nowhere Man'
},
{
label: 'Think For Yourself',
key: 'Think For Yourself'
},
{
label: 'The Word',
key: 'The Word'
},
{
label: 'Michelle',
key: 'Michelle',
disabled: true
},
{
label: 'What goes on',
key: 'What goes on'
},
{
label: 'Girl',
key: 'Girl'
},
{
label: "I'm looking through you",
key: "I'm looking through you"
},
{
label: 'In My Life',
key: 'In My Life'
},
{
label: 'Wait',
key: 'Wait'
}
]
},
{
label: 'Let It Be',
key: 'Let It Be Album',
children: [
{
label: 'Two Of Us',
key: 'Two Of Us'
},
{
label: 'Dig A Pony',
key: 'Dig A Pony'
},
{
label: 'Across The Universe',
key: 'Across The Universe'
},
{
label: 'I Me Mine',
key: 'I Me Mine'
},
{
label: 'Dig It',
key: 'Dig It'
},
{
label: 'Let It Be',
key: 'Let It Be'
},
{
label: 'Maggie Mae',
key: 'Maggie Mae'
},
{
label: "I've Got A Feeling",
key: "I've Got A Feeling"
},
{
label: 'One After 909',
key: 'One After 909'
},
{
label: 'The Long And Winding Road',
key: 'The Long And Winding Road'
},
{
label: 'For You Blue',
key: 'For You Blue'
},
{
label: 'Get Back',
key: 'Get Back'
}
]
}
]
}
}
})
</script>
```
### 单图上传 UploadImage
```vue
<template>
<UploadImage :maxNumber="1" v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import UploadImage from '@/components/Upload/uploadImage.vue';
const value = ref(null);
</script>
```
### 多图上传 UploadImage
```vue
<template>
<UploadImage :maxNumber="10" v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import UploadImage from '@/components/Upload/uploadImage.vue';
const value = ref(null);
</script>
```
### 单文件上传 UploadFile
```vue
<template>
<UploadFile :maxNumber="1" v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import UploadFile from '@/components/Upload/uploadFile.vue';
const value = ref(null);
</script>
```
### 多文件上传 UploadFile
```vue
<template>
<UploadFile :maxNumber="10" v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import UploadFile from '@/components/Upload/uploadFile.vue';
const value = ref(null);
</script>
```
### 开关 Switch
```vue
<template>
<n-switch v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 评分 Rate
```vue
<template>
<n-rate allow-half :default-value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref(null);
</script>
```
### 省市区选择器 CitySelector
```vue
<template>
<CitySelector v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import CitySelector from '@/components/CitySelector/citySelector.vue';
const value = ref(null);
</script>
```
### 图标选择器 IconSelector
```vue
<template>
<IconSelector style="width: 100%" v-model:value="value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import IconSelector from '@/components/IconSelector/index.vue';
const value = ref(null);
</script>
```
更多组件请参考https://www.naiveui.com/zh-CN/os-theme/components/button

View File

@ -72,4 +72,6 @@ type GroupSelectReq struct {
g.Meta `path:"/cronGroup/select" method:"get" tags:"定时任务分组" summary:"定时任务分组选项"`
}
type GroupSelectRes sysin.DictTypeSelectModel
type GroupSelectRes struct {
*sysin.CronGroupSelectModel
}

View File

@ -22,14 +22,6 @@ type NameUniqueRes struct {
IsUnique bool `json:"is_unique" dc:"是否唯一"`
}
// ListTreeReq 查询列表树
type ListTreeReq struct {
Id int64 `json:"id" dc:"部门ID"`
g.Meta `path:"/dept/list_tree" method:"get" tags:"部门" summary:"获取部门列表树"`
}
type ListTreeRes []*adminin.DeptListTreeModel
// ListReq 查询列表
type ListReq struct {
Name string `json:"name" dc:"部门名称"`
@ -37,7 +29,9 @@ type ListReq struct {
g.Meta `path:"/dept/list" method:"get" tags:"部门" summary:"获取部门列表"`
}
type ListRes adminin.DeptListModel
type ListRes struct {
adminin.DeptListModel
}
// ViewReq 获取指定信息
type ViewReq struct {

View File

@ -17,7 +17,7 @@ type TypeTreeReq struct {
g.Meta `path:"/dictType/tree" tags:"字典类型" method:"get" summary:"字典类型树列表"`
}
type TypeTreeRes struct {
List []map[string]interface{} `json:"list" dc:"数据列表"`
List []*sysin.DictTypeTree `json:"list" dc:"数据列表"`
}
// TypeEditReq 修改/新增字典数据
@ -34,10 +34,3 @@ type TypeDeleteReq struct {
g.Meta `path:"/dictType/delete" method:"post" tags:"字典类型" summary:"删除字典类型"`
}
type TypeDeleteRes struct{}
// TypeSelectReq 修改/新增字典数据
type TypeSelectReq struct {
g.Meta `path:"/dictType/select" method:"get" tags:"字典类型" summary:"字典类型选项"`
}
type TypeSelectRes sysin.DictTypeSelectModel

View File

@ -38,21 +38,11 @@ type MemberListRes struct {
// ListReq 查询列表
type ListReq struct {
g.Meta `path:"/role/list" method:"get" tags:"角色" summary:"获取角色列表"`
form.PageReq
form.RangeDateReq
form.StatusReq
DeptId int `json:"deptId" description:"部门ID"`
Mobile int `json:"mobile" description:"手机号"`
Username string `json:"username" description:"用户名"`
Realname string `json:"realname" description:"真实姓名"`
StartTime string `json:"start_time" description:"开始时间"`
EndTime string `json:"end_time" description:"结束时间"`
Name string `json:"name" description:"岗位名称"`
Code string `json:"code" description:"岗位编码"`
adminin.RoleListInp
}
type ListRes struct {
List []g.Map `json:"list" description:"数据列表"`
*adminin.RoleListModel
form.PageRes
}

View File

@ -2,15 +2,15 @@
# https://goframe.org/pages/viewpage.action?pageId=3673173
gfcli:
build:
name: "hotgo"
# arch: "all" #amd64
# system: "all" #linux
name: "hotgo" # 编译后的可执行文件名称
# arch: "all" #不填默认当前系统架构可选386,amd64,arm,all
# system: "all" #不填默认当前系统平台可选linux,darwin,windows,all
mod: "none"
cgo: 0
packSrc: "resource"
packDst: "internal/packed/packed.go"
packSrc: "resource" # 将resource目录打包进可执行文件静态资源无需单独部署
packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径一般使用相对路径指定到本项目目录中
version: ""
output: "./temp/hotgo"
output: "./temp/hotgo" # 可执行文件生成路径
extra: ""
# gf生成代码如果你想调整hotgo中代码生成的相关daoservice代码同样也受用于此配置

View File

@ -41,7 +41,7 @@ var (
---------------------------------------------------------------------------------
更多
github地址https://github.com/bufanyun/hotgo
文档地址https://github.com/bufanyun/hotgo/tree/v2.0/docs
文档地址https://github.com/bufanyun/hotgo/tree/v2.0/docs/guide-zh-CN
HotGo框架交流1群190966648
`,
}

View File

@ -3,14 +3,13 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"hotgo/internal/queues"
"hotgo/internal/library/queue"
)
var (
@ -20,7 +19,8 @@ var (
Description: ``,
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
g.Log().Debug(ctx, "start queue consumer..")
queues.Run(ctx)
queue.StartConsumersListener(ctx)
g.Log().Debug(ctx, "start queue consumer success..")
return
},
}

View File

@ -7,5 +7,5 @@ package consts
// VersionApp HotGo版本
const (
VersionApp = "2.2.10"
VersionApp = "2.3.5"
)

View File

@ -90,23 +90,8 @@ func (c *cDept) List(ctx context.Context, req *dept.ListReq) (res *dept.ListRes,
return
}
res = (*dept.ListRes)(&data)
return
}
// ListTree 查看列表树
func (c *cDept) ListTree(ctx context.Context, req *dept.ListTreeReq) (res *dept.ListTreeRes, err error) {
var in adminin.DeptListTreeInp
if err = gconv.Scan(req, &in); err != nil {
return
}
data, err := service.AdminDept().ListTree(ctx, in)
if err != nil {
return
}
res = (*dept.ListTreeRes)(&data)
res = new(dept.ListRes)
res.List = data.List
return
}

View File

@ -15,6 +15,7 @@ import (
"hotgo/internal/model/input/adminin"
"hotgo/internal/model/input/form"
"hotgo/internal/service"
"hotgo/utility/validate"
)
var (
@ -165,6 +166,10 @@ func (c *cMember) Edit(ctx context.Context, req *member.EditReq) (res *member.Ed
return
}
if err = validate.PreFilter(ctx, &in); err != nil {
return
}
in.PostIds = req.PostIds
err = service.AdminMember().Edit(ctx, in)
return

View File

@ -55,7 +55,7 @@ func (c *cRole) List(ctx context.Context, req *role.ListReq) (res *role.ListRes,
}
res = new(role.ListRes)
res.List = list
res.RoleListModel = list
res.PageCount = form.CalPageCount(totalCount, req.PerPage)
res.Page = req.Page
res.PerPage = req.PerPage

View File

@ -100,11 +100,12 @@ func (c *cCronGroup) Status(ctx context.Context, req *cron.GroupStatusReq) (res
// Select 选项
func (c *cCronGroup) Select(ctx context.Context, req *cron.GroupSelectReq) (res *cron.GroupSelectRes, err error) {
list, err := service.SysCronGroup().Select(ctx, sysin.CronGroupSelectInp{})
data, err := service.SysCronGroup().Select(ctx, sysin.CronGroupSelectInp{})
if err != nil {
return
}
res = (*cron.GroupSelectRes)(&list)
res = new(cron.GroupSelectRes)
res.CronGroupSelectModel = data
return
}

View File

@ -48,14 +48,3 @@ func (c *cDictType) Edit(ctx context.Context, req *dict.TypeEditReq) (res *dict.
err = service.SysDictType().Edit(ctx, in)
return
}
// Select 选项
func (c *cDictType) Select(ctx context.Context, req *dict.TypeSelectReq) (res *dict.TypeSelectRes, err error) {
list, err := service.SysDictType().Select(ctx, sysin.DictTypeSelectInp{})
if err != nil {
return
}
res = (*dict.TypeSelectRes)(&list)
return
}

View File

@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package admin
import (
@ -71,7 +70,7 @@ func (c *cMonitor) RunInfo(client *websocket.Client, req *websocket.WRequest) {
"goName": "Golang",
"version": runtime.Version(),
"startTime": meta.STartTime,
"runTime": gtime.Now().Timestamp() - meta.STartTime.Timestamp(),
"runTime": gtime.Now().Timestamp() - meta.STartTime,
"rootPath": runtime.GOROOT(),
"pwd": pwd,
"goroutine": runtime.NumGoroutine(),
@ -108,7 +107,7 @@ func (c *cMonitor) Trends(client *websocket.Client, req *websocket.WRequest) {
mMemUsed float64
mDisk, _ = disk.Usage("/")
mProcess, _ = process.Pids()
mLoadAvg *model.LoadAvgStats
mLoadAvg = new(model.LoadAvgStats)
data = g.Map{}
monitorHeads []MonitorHead
nets []NetC

View File

@ -13,20 +13,24 @@ import (
// Build 构建新插件
func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err error) {
buildPath := "./" + consts.AddonsDir + "/" + sk.Name
modulesPath := "./" + consts.AddonsDir + "/modules/" + sk.Name + ".go"
templatePath := gstr.Replace(conf.TemplatePath, "{$name}", sk.Name)
replaces := map[string]string{
"@{.label}": sk.Label,
"@{.name}": sk.Name,
"@{.group}": strconv.Itoa(sk.Group),
"@{.brief}": sk.Brief,
"@{.description}": sk.Description,
"@{.author}": sk.Author,
"@{.version}": sk.Version,
}
var (
buildPath = "./" + consts.AddonsDir + "/" + sk.Name
modulesPath = "./" + consts.AddonsDir + "/modules/" + sk.Name + ".go"
templatePath = gstr.Replace(conf.TemplatePath, "{$name}", sk.Name)
webApiPath = gstr.Replace(conf.WebApiPath, "{$name}", sk.Name)
webViewsPath = gstr.Replace(conf.WebViewsPath, "{$name}", sk.Name)
replaces = map[string]string{
"@{.label}": sk.Label,
"@{.name}": sk.Name,
"@{.group}": strconv.Itoa(sk.Group),
"@{.brief}": sk.Brief,
"@{.description}": sk.Description,
"@{.author}": sk.Author,
"@{.version}": sk.Version,
}
)
if err = checkBuildDir(buildPath, modulesPath, templatePath); err != nil {
if err = checkBuildDir(buildPath, modulesPath, templatePath, webApiPath, webViewsPath); err != nil {
return
}
@ -49,7 +53,6 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
gfile.RealPath(conf.SrcPath): "",
".template": "",
})
flowFile = buildPath + "/" + flowFile
content := gstr.ReplaceByMap(gfile.GetContents(path), replaces)
@ -59,11 +62,31 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
}
}
if err = gfile.PutContents(templatePath+"/home/index.html", homeLayout); err != nil {
// 隐式注入插件
if err = gfile.PutContents(modulesPath, gstr.ReplaceByMap(importModules, replaces)); err != nil {
return
}
// home默认页面
if err = gfile.PutContents(templatePath+"/home/index.html", gstr.ReplaceByMap(homeLayout, replaces)); err != nil {
return
}
// webApi
if err = gfile.PutContents(webApiPath+"/config/index.ts", gstr.ReplaceByMap(webApiLayout, replaces)); err != nil {
return
}
// web插件配置主页面
if err = gfile.PutContents(webViewsPath+"/config/BasicSetting.vue", gstr.ReplaceByMap(webConfigBasicSetting, replaces)); err != nil {
return
}
// web插件基础配置页面
if err = gfile.PutContents(webViewsPath+"/config/system.vue", gstr.ReplaceByMap(webConfigSystem, replaces)); err != nil {
return
}
err = gfile.PutContents(modulesPath, gstr.ReplaceByMap(importModules, replaces))
return
}
@ -79,48 +102,3 @@ func checkBuildDir(paths ...string) error {
}
return nil
}
const (
importModules = `// Package modules
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package modules
import _ "hotgo/addons/@{.name}"
`
homeLayout = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
<meta name="keywords" content="@{.Keywords}"/>
<meta name="description" content="@{.Description}"/>
<title>@{.Title}</title>
<script type="text/javascript" src="/resource/home/js/jquery-3.6.0.min.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #f6f6f6;
}
</style>
</head>
<body>
<div style="padding-top: 100px;text-align:center;">
<h1><p>Hello@{.Data.name}!!</p></h1>
<h2><p>@{.Data.module}</p></h2>
<h2><p>服务器时间@{.Data.time}</p></h2>
</div>
</body>
<script>
</script>
</html>`
)

View File

@ -0,0 +1,225 @@
package addons
const (
importModules = `// Package modules
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package modules
import _ "hotgo/addons/@{.name}"
`
homeLayout = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
<meta name="keywords" content="@{.Keywords}"/>
<meta name="description" content="@{.Description}"/>
<title>@{.Title}</title>
<script type="text/javascript" src="/resource/home/js/jquery-3.6.0.min.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #f6f6f6;
}
</style>
</head>
<body>
<div style="padding-top: 100px;text-align:center;">
<h1><p>Hello@{.Data.name}!!</p></h1>
<h2><p>@{.Data.module}</p></h2>
<h2><p>服务器时间@{.Data.time}</p></h2>
</div>
</body>
<script>
</script>
</html>`
webApiLayout = `import { http } from '@/utils/http/axios';
export function getConfig(params) {
return http.request({
url: '/@{.name}/config/get',
method: 'get',
params,
});
}
export function updateConfig(params) {
return http.request({
url: '/@{.name}/config/update',
method: 'post',
params,
});
}
`
webConfigBasicSetting = `<template>
<div>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="测试参数" path="basicTest">
<n-input v-model:value="formValue.basicTest" placeholder="请输入测试参数" />
<template #feedback>
这是一个测试参数每个插件都可以有独立的配置项可以按需添加</template
>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { useMessage } from 'naive-ui';
import { getConfig, updateConfig } from '@/api/addons/@{.name}/config';
const group = ref('basic');
const show = ref(false);
const rules = {
basicTest: {
required: true,
message: '请输入测试参数',
trigger: 'blur',
},
};
const formRef: any = ref(null);
const message = useMessage();
const formValue = ref({
basicTest: 'HotGo',
});
function formSubmit() {
formRef.value.validate((errors) => {
if (!errors) {
updateConfig({ group: group.value, list: formValue.value }).then((_res) => {
message.success('更新成功');
load();
});
} else {
message.error('验证失败请填写完整信息');
}
});
}
onMounted(() => {
load();
});
function load() {
show.value = true;
new Promise((_resolve, _reject) => {
getConfig({ group: group.value })
.then((res) => {
formValue.value = res.list;
})
.finally(() => {
show.value = false;
});
});
}
</script>
`
webConfigSystem = `<template>
<div>
<n-grid cols="24 300:1 600:24" :x-gap="24">
<n-grid-item span="6">
<n-card :bordered="false" size="small" class="proCard">
<n-thing
class="thing-cell"
v-for="item in typeTabList"
:key="item.key"
:class="{ 'thing-cell-on': type === item.key }"
@click="switchType(item)"
>
<template #header>{{ item.name }}</template>
<template #description>{{ item.desc }}</template>
</n-thing>
</n-card>
</n-grid-item>
<n-grid-item span="18">
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
<BasicSetting v-if="type === 1" />
</n-card>
</n-grid-item>
</n-grid>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
import BasicSetting from './BasicSetting.vue';
const typeTabList = [
{
name: '基本设置',
desc: '系统常规设置',
key: 1,
},
];
export default defineComponent({
components: {
BasicSetting,
},
setup() {
const state = reactive({
type: 1,
typeTitle: '基本设置',
});
function switchType(e) {
state.type = e.key;
state.typeTitle = e.name;
}
return {
...toRefs(state),
switchType,
typeTabList,
};
},
});
</script>
<style lang="less" scoped>
.thing-cell {
margin: 0 -16px 10px;
padding: 5px 16px;
&:hover {
background: #f3f3f3;
cursor: pointer;
}
}
.thing-cell-on {
background: #f0faff;
color: #2d8cf0;
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
color: #2d8cf0;
}
&:hover {
background: #f0faff;
}
}
</style>
`
)

View File

@ -66,11 +66,12 @@ func RegisterModulesRouter(ctx context.Context, group *ghttp.RouterGroup) {
func RegisterModule(m Module) Module {
mLock.Lock()
defer mLock.Unlock()
_, ok := modules[m.GetSkeleton().Name]
name := m.GetSkeleton().Name
_, ok := modules[name]
if ok {
panic("module repeat registration, name:" + m.GetSkeleton().Name)
panic("module repeat registration, name:" + name)
}
modules[m.GetSkeleton().Name] = m
modules[name] = m
return m
}

View File

@ -107,6 +107,15 @@ func GetRoleKey(ctx context.Context) string {
return user.RoleKey
}
// GetModule 获取应用模块
func GetModule(ctx context.Context) string {
c := Get(ctx)
if c == nil {
return ""
}
return c.Module
}
// SetAddonName 设置插件信息
func SetAddonName(ctx context.Context, name string) {
c := Get(ctx)

View File

@ -256,11 +256,6 @@ func setDefaultQuery(field *sysin.GenCodesColumnListModel) {
return
}
if field.Index == consts.GenCodesIndexPK {
field.IsQuery = true
return
}
if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
field.IsQuery = true
return

View File

@ -126,7 +126,7 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
}
setupBuffer.WriteString(" function loadForm(value) {\n loading.value = true;\n\n // 新增\n if (value.id < 1) {\n params.value = newState(value);\n MaxSort()\n .then((res) => {\n params.value.sort = res.sort;\n })\n .finally(() => {\n loading.value = false;\n });\n return;\n }\n\n // 编辑\n View({ id: value.id })\n .then((res) => {\n params.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }\n\n watch(\n () => props.formParams,\n (value) => {\n loadForm(value);\n }\n );")
} else {
importBuffer.WriteString(" import { onMounted, ref, computed } from 'vue';\n")
importBuffer.WriteString(" import { onMounted, ref, computed, watch } from 'vue';\n")
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
importBuffer.WriteString(" import { Edit, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n")
} else {

View File

@ -52,11 +52,11 @@ func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
err := g.Model("admin_role").Where("id", co.User.RoleId).Scan(&role)
if err != nil {
g.Log().Fatalf(ctx, "failed to role information err:%+v", err)
g.Log().Panicf(ctx, "failed to role information err:%+v", err)
}
if role == nil {
g.Log().Fatalf(ctx, "failed to role information roleModel == nil")
g.Log().Panicf(ctx, "failed to role information roleModel == nil")
}
sq := g.Model("admin_member").Fields("id")
@ -77,7 +77,7 @@ func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
case consts.RoleDataSelfAndAllSub: // 自己和全部下级
m = m.WhereIn(filterField, GetSelfAndAllSub(co.User.Id))
default:
g.Log().Fatalf(ctx, "dataScope is not registered")
g.Log().Panicf(ctx, "dataScope is not registered")
}
return m

View File

@ -0,0 +1,73 @@
package queue
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"sync"
)
// consumerStrategy 消费者策略,实现该接口即可加入到消费队列中
type consumerStrategy interface {
GetTopic() string // 获取消费主题
Handle(ctx context.Context, mqMsg MqMsg) (err error) // 处理消息
}
// consumerManager 消费者管理
type consumerManager struct {
sync.Mutex
list map[string]consumerStrategy // 维护的消费者列表
}
var consumers = &consumerManager{
list: make(map[string]consumerStrategy),
}
// RegisterConsumer 注册任务到消费者队列
func RegisterConsumer(cs consumerStrategy) {
consumers.Lock()
defer consumers.Unlock()
topic := cs.GetTopic()
if _, ok := consumers.list[topic]; ok {
g.Log().Debugf(ctx, "queue.RegisterConsumer topic:%v duplicate registration.", topic)
return
}
consumers.list[topic] = cs
}
// StartConsumersListener 启动所有已注册的消费者监听
func StartConsumersListener(ctx context.Context) {
for _, consumer := range consumers.list {
go func(consumer consumerStrategy) {
consumerListen(ctx, consumer)
}(consumer)
}
}
// consumerListen 消费者监听
func consumerListen(ctx context.Context, job consumerStrategy) {
var (
topic = job.GetTopic()
consumer, err = InstanceConsumer()
)
if err != nil {
g.Log().Fatalf(ctx, "InstanceConsumer %s err:%+v", topic, err)
return
}
if listenErr := consumer.ListenReceiveMsgDo(topic, func(mqMsg MqMsg) {
err = job.Handle(ctx, mqMsg)
if err != nil {
// 遇到错误,重新加入到队列
//queue.Push(topic, mqMsg.Body)
}
// 记录消费队列日志
ConsumerLog(ctx, topic, mqMsg, err)
}); listenErr != nil {
g.Log().Fatalf(ctx, "消费队列:%s 监听失败, err:%+v", topic, listenErr)
}
}

View File

@ -11,6 +11,8 @@ import (
"time"
)
// Disk 磁盘队列
type DiskProducerMq struct {
config *disk.Config
producers map[string]*disk.Queue

View File

@ -66,7 +66,7 @@ func CreateClient(accessKeyId *string, accessKeySecret *string) (_result *dysmsa
return _result, _err
}
func Send(accessKeyId string, accessKeySecret string) (_err error) {
func TestSend(accessKeyId string, accessKeySecret string) (_err error) {
// 工程代码泄露可能会导致AccessKey泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议使用更安全的 STS 方式更多鉴权访问方式请参见https://help.aliyun.com/document_detail/378661.html
client, _err := CreateClient(tea.String(accessKeyId), tea.String(accessKeySecret))
if _err != nil {

View File

@ -10,7 +10,6 @@ import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/library/hgorm"
@ -18,7 +17,6 @@ import (
"hotgo/internal/model/input/adminin"
"hotgo/internal/service"
"hotgo/utility/convert"
"hotgo/utility/tree"
"hotgo/utility/validate"
)
@ -33,27 +31,21 @@ func init() {
}
// NameUnique 菜单名称是否唯一
func (s *sAdminDept) NameUnique(ctx context.Context, in adminin.DeptNameUniqueInp) (*adminin.DeptNameUniqueModel, error) {
var res adminin.DeptNameUniqueModel
func (s *sAdminDept) NameUnique(ctx context.Context, in adminin.DeptNameUniqueInp) (res *adminin.DeptNameUniqueModel, err error) {
isUnique, err := dao.AdminDept.IsUniqueName(ctx, in.Id, in.Name)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return nil, err
return
}
res = new(adminin.DeptNameUniqueModel)
res.IsUnique = isUnique
return &res, nil
return
}
// Delete 删除
func (s *sAdminDept) Delete(ctx context.Context, in adminin.DeptDeleteInp) (err error) {
var (
models *entity.AdminDept
)
err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Scan(&models)
if err != nil {
var models *entity.AdminDept
if err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Scan(&models); err != nil {
return err
}
@ -71,115 +63,86 @@ func (s *sAdminDept) Delete(ctx context.Context, in adminin.DeptDeleteInp) (err
}
_, err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Delete()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
// Edit 修改/新增
func (s *sAdminDept) Edit(ctx context.Context, in adminin.DeptEditInp) (err error) {
if in.Name == "" {
err = gerror.New("名称不能为空")
return err
return
}
uniqueName, err := dao.AdminDept.IsUniqueName(ctx, in.Id, in.Name)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
return
}
if !uniqueName {
err = gerror.New("名称已存在")
return err
return
}
in.Pid, in.Level, in.Tree, err = hgorm.GenSubTree(ctx, dao.AdminDept, in.Pid)
if err != nil {
return err
if in.Pid, in.Level, in.Tree, err = hgorm.GenSubTree(ctx, dao.AdminDept, in.Pid); err != nil {
return
}
// 修改
if in.Id > 0 {
_, err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Data(in).Update()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
// 新增
_, err = dao.AdminDept.Ctx(ctx).Data(in).Insert()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
// Status 更新部门状态
func (s *sAdminDept) Status(ctx context.Context, in adminin.DeptStatusInp) (err error) {
if in.Id <= 0 {
err = gerror.New("ID不能为空")
return err
return
}
if in.Status <= 0 {
err = gerror.New("状态不能为空")
return err
return
}
if !validate.InSliceInt(consts.StatusMap, in.Status) {
err = gerror.New("状态不正确")
return err
return
}
// 修改
in.UpdatedAt = gtime.Now()
_, err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Data("status", in.Status).Update()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
// MaxSort 最大排序
func (s *sAdminDept) MaxSort(ctx context.Context, in adminin.DeptMaxSortInp) (*adminin.DeptMaxSortModel, error) {
var res adminin.DeptMaxSortModel
func (s *sAdminDept) MaxSort(ctx context.Context, in adminin.DeptMaxSortInp) (res *adminin.DeptMaxSortModel, err error) {
res = new(adminin.DeptMaxSortModel)
if in.Id > 0 {
if err := dao.AdminDept.Ctx(ctx).Where("id", in.Id).Order("sort desc").Scan(&res); err != nil {
if err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Order("sort desc").Scan(&res); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return nil, err
return
}
}
res.Sort = res.Sort + 10
return &res, nil
return
}
// View 获取指定字典类型信息
func (s *sAdminDept) View(ctx context.Context, in adminin.DeptViewInp) (res *adminin.DeptViewModel, err error) {
if err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Scan(&res); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return nil, err
}
return res, nil
err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Scan(&res)
return
}
// List 获取列表
func (s *sAdminDept) List(ctx context.Context, in adminin.DeptListInp) (list adminin.DeptListModel, err error) {
func (s *sAdminDept) List(ctx context.Context, in adminin.DeptListInp) (res *adminin.DeptListModel, err error) {
var (
mod = dao.AdminDept.Ctx(ctx)
models []*entity.AdminDept
@ -228,114 +191,12 @@ func (s *sAdminDept) List(ctx context.Context, in adminin.DeptListInp) (list adm
if err = mod.Order("pid asc,sort asc").Scan(&models); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, err
return
}
list = gconv.SliceMap(models)
for k, v := range list {
list[k]["index"] = v["id"]
list[k]["key"] = v["id"]
list[k]["label"] = v["name"]
}
return tree.GenTree(list), nil
}
type DeptTree struct {
entity.AdminDept
Children []*DeptTree `json:"children"`
}
// getDeptChildIds 将列表转为父子关系列表
func (s *sAdminDept) getDeptChildIds(ctx context.Context, lists []*DeptTree, pid int64) []*DeptTree {
var (
count = len(lists)
newLists []*DeptTree
)
if count == 0 {
return nil
}
for i := 0; i < len(lists); i++ {
if lists[i].Id > 0 && lists[i].Pid == pid {
var row *DeptTree
if err := gconv.Structs(lists[i], &row); err != nil {
panic(err)
}
row.Children = s.getDeptChildIds(ctx, lists, row.Id)
newLists = append(newLists, row)
}
}
return newLists
}
type DeptListTree struct {
Id int64 `json:"id" `
Key int64 `json:"key" `
Pid int64 `json:"pid" `
Label string `json:"label"`
Title string `json:"title"`
Name string `json:"name"`
Type string `json:"type"`
Children []*DeptListTree `json:"children"`
}
// ListTree 获取列表树
func (s *sAdminDept) ListTree(ctx context.Context, in adminin.DeptListTreeInp) (list []*adminin.DeptListTreeModel, err error) {
var (
mod = dao.AdminDept.Ctx(ctx)
dataList []*entity.AdminDept
models []*DeptListTree
)
err = mod.Order("id desc").Scan(&dataList)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, err
}
_ = gconv.Structs(dataList, &models)
// 重写树入参
for i := 0; i < len(models); i++ {
models[i].Key = models[i].Id
models[i].Title = models[i].Name
models[i].Label = models[i].Name
}
childIds := s.getDeptTreeChildIds(ctx, models, 0)
_ = gconv.Structs(childIds, &list)
return list, nil
}
// getDeptTreeChildIds 将列表转为父子关系列表
func (s *sAdminDept) getDeptTreeChildIds(ctx context.Context, lists []*DeptListTree, pid int64) []*DeptListTree {
var (
count = len(lists)
newLists []*DeptListTree
)
if count == 0 {
return nil
}
for i := 0; i < len(lists); i++ {
if lists[i].Id > 0 && lists[i].Pid == pid {
var row *DeptListTree
if err := gconv.Structs(lists[i], &row); err != nil {
panic(err)
}
row.Children = s.getDeptTreeChildIds(ctx, lists, row.Id)
newLists = append(newLists, row)
}
}
return newLists
res = new(adminin.DeptListModel)
res.List = s.treeList(0, models)
return
}
// GetName 获取部门名称
@ -352,3 +213,23 @@ func (s *sAdminDept) GetName(ctx context.Context, id int64) (name string, err er
return data.Name, nil
}
// treeList 树状列表
func (s *sAdminDept) treeList(pid int64, nodes []*entity.AdminDept) (list []*adminin.DeptTree) {
list = make([]*adminin.DeptTree, 0)
for _, v := range nodes {
if v.Pid == pid {
item := new(adminin.DeptTree)
item.AdminDept = *v
item.Label = v.Name
item.Value = v.Id
child := s.treeList(v.Id, nodes)
if len(child) > 0 {
item.Children = child
}
list = append(list, item)
}
}
return
}

View File

@ -256,6 +256,7 @@ func (s *sAdminMember) ResetPwd(ctx context.Context, in adminin.MemberResetPwdIn
memberInfo *entity.AdminMember
memberId = contexts.GetUserId(ctx)
)
if err = s.FilterAuthModel(ctx, memberId).Where("id", in.Id).Scan(&memberInfo); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
@ -400,10 +401,18 @@ func (s *sAdminMember) Edit(ctx context.Context, in adminin.MemberEditInp) (err
return gerror.New("超管账号禁止编辑!")
}
// 权限验证
var mm = s.FilterAuthModel(ctx, opMemberId).Where("id", in.Id)
_, err = mm.Data(in).Update()
if err != nil {
mod := s.FilterAuthModel(ctx, opMemberId)
if in.Password != "" {
salt, err := s.FilterAuthModel(ctx, contexts.GetUserId(ctx)).Fields(dao.AdminMember.Columns().Salt).Where("id", in.Id).Value()
if err != nil {
return err
}
in.PasswordHash = gmd5.MustEncryptString(in.Password + salt.String())
} else {
mod = mod.FieldsEx(dao.AdminMember.Columns().PasswordHash)
}
if _, err = mod.Where("id", in.Id).Data(in).Update(); err != nil {
return gerror.Wrap(err, consts.ErrorORM)
}

View File

@ -25,18 +25,18 @@ func init() {
service.RegisterAdminMemberPost(NewAdminMemberPost())
}
func (s *sAdminMemberPost) UpdatePostIds(ctx context.Context, member_id int64, post_ids []int64) (err error) {
_, err = dao.AdminMemberPost.Ctx(ctx).Where("member_id", member_id).Delete()
func (s *sAdminMemberPost) UpdatePostIds(ctx context.Context, memberId int64, postIds []int64) (err error) {
_, err = dao.AdminMemberPost.Ctx(ctx).Where("member_id", memberId).Delete()
if err != nil {
err = gerror.Wrap(err, "删除失败")
return err
}
for i := 0; i < len(post_ids); i++ {
for i := 0; i < len(postIds); i++ {
_, err = dao.AdminMemberPost.Ctx(ctx).
Insert(entity.AdminMemberPost{
MemberId: member_id,
PostId: post_ids[i],
MemberId: memberId,
PostId: postIds[i],
})
if err != nil {
err = gerror.Wrap(err, "插入用户岗位失败")
@ -48,20 +48,20 @@ func (s *sAdminMemberPost) UpdatePostIds(ctx context.Context, member_id int64, p
}
// GetMemberByIds 获取指定用户的岗位ids
func (s *sAdminMemberPost) GetMemberByIds(ctx context.Context, member_id int64) (post_ids []int64, err error) {
func (s *sAdminMemberPost) GetMemberByIds(ctx context.Context, memberId int64) (postIds []int64, err error) {
var list []*entity.AdminMemberPost
err = dao.AdminMemberPost.Ctx(ctx).
Fields("post_id").
Where("member_id", member_id).
Where("member_id", memberId).
Scan(&list)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return post_ids, err
return postIds, err
}
for i := 0; i < len(list); i++ {
post_ids = append(post_ids, list[i].PostId)
postIds = append(postIds, list[i].PostId)
}
return post_ids, nil
return postIds, nil
}

View File

@ -34,7 +34,7 @@ func init() {
// StartMonitor 启动服务监控
func (s *sAdminMonitor) StartMonitor(ctx context.Context) {
simple.SafeGo(ctx, func(ctx context.Context) {
s.data.STartTime = gtime.Now()
s.data.STartTime = gtime.Now().Timestamp()
intranetIP, err := location.GetLocalIP()
if err != nil {
g.Log().Infof(ctx, "parse intranetIP err:%+v", err)

View File

@ -12,7 +12,6 @@ import (
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/api/admin/role"
"hotgo/internal/consts"
"hotgo/internal/dao"
@ -25,9 +24,7 @@ import (
"hotgo/internal/service"
"hotgo/utility/auth"
"hotgo/utility/convert"
"hotgo/utility/tree"
"sort"
"strconv"
)
type sAdminRole struct{}
@ -69,31 +66,26 @@ func (s *sAdminRole) Verify(ctx context.Context, path, method string) bool {
}
// List 获取列表
func (s *sAdminRole) List(ctx context.Context, in adminin.RoleListInp) (list []g.Map, totalCount int, err error) {
func (s *sAdminRole) List(ctx context.Context, in adminin.RoleListInp) (res *adminin.RoleListModel, totalCount int, err error) {
var (
mod = dao.AdminRole.Ctx(ctx)
models []*adminin.RoleListModel
models []*entity.AdminRole
)
totalCount, err = mod.Count()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, totalCount, err
return
}
err = mod.Page(in.Page, in.PerPage).Order("id asc").Scan(&models)
if err != nil {
if err = mod.Page(in.Page, in.PerPage).Order("sort asc,id asc").Scan(&models); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, totalCount, err
return
}
for _, v := range models {
v.Label = v.Name
v.Value = v.Id
v.Key = strconv.FormatInt(v.Id, 10)
}
return tree.GenTree(gconv.SliceMap(models)), totalCount, err
res = new(adminin.RoleListModel)
res.List = s.treeList(0, models)
return
}
// GetName 获取指定角色的名称
@ -134,6 +126,7 @@ func (s *sAdminRole) GetPermissions(ctx context.Context, reqInfo *role.GetPermis
if err != nil {
return nil, err
}
if len(values) == 0 {
return
}
@ -156,6 +149,10 @@ func (s *sAdminRole) UpdatePermissions(ctx context.Context, reqInfo *role.Update
if len(reqInfo.MenuIds) == 0 {
return nil
}
// 去重
reqInfo.MenuIds = convert.UniqueSliceInt64(reqInfo.MenuIds)
addMap := make(g.List, 0, len(reqInfo.MenuIds))
for _, v := range reqInfo.MenuIds {
addMap = append(addMap, g.Map{
@ -163,8 +160,8 @@ func (s *sAdminRole) UpdatePermissions(ctx context.Context, reqInfo *role.Update
"menu_id": v,
})
}
_, err = dao.AdminRoleMenu.Ctx(ctx).Data(addMap).Insert()
if err != nil {
if _, err = dao.AdminRoleMenu.Ctx(ctx).Data(addMap).Insert(); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
@ -176,56 +173,47 @@ func (s *sAdminRole) UpdatePermissions(ctx context.Context, reqInfo *role.Update
func (s *sAdminRole) Edit(ctx context.Context, in *role.EditReq) (err error) {
if in.Name == "" {
err = gerror.New("名称不能为空")
return err
return
}
if in.Key == "" {
err = gerror.New("编码不能为空")
return err
return
}
uniqueName, err := dao.AdminRole.IsUniqueName(ctx, in.Id, in.Name)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
return
}
if !uniqueName {
err = gerror.New("名称已存在")
return err
return
}
uniqueCode, err := dao.AdminRole.IsUniqueCode(ctx, in.Id, in.Key)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
return
}
if !uniqueCode {
err = gerror.New("编码已存在")
return err
return
}
in.Pid, in.Level, in.Tree, err = hgorm.GenSubTree(ctx, dao.AdminRole, in.Pid)
if err != nil {
return err
if in.Pid, in.Level, in.Tree, err = hgorm.GenSubTree(ctx, dao.AdminRole, in.Pid); err != nil {
return
}
// 修改
if in.Id > 0 {
_, err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Data(in).Update()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
// 新增
_, err = dao.AdminRole.Ctx(ctx).Data(in).Insert()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
func (s *sAdminRole) Delete(ctx context.Context, in *role.DeleteReq) (err error) {
@ -233,12 +221,9 @@ func (s *sAdminRole) Delete(ctx context.Context, in *role.DeleteReq) (err error)
return gerror.New("ID不正确")
}
var (
models *entity.AdminRole
)
err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Scan(&models)
if err != nil {
return err
var models *entity.AdminRole
if err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Scan(&models); err != nil {
return
}
if models == nil {
@ -255,12 +240,7 @@ func (s *sAdminRole) Delete(ctx context.Context, in *role.DeleteReq) (err error)
}
_, err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Delete()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
func (s *sAdminRole) DataScopeSelect(ctx context.Context) (res form.Selects) {
@ -285,8 +265,7 @@ func (s *sAdminRole) DataScopeEdit(ctx context.Context, in *adminin.DataScopeEdi
superRoleKey = g.Cfg().MustGet(ctx, "hotgo.admin.superRoleKey")
)
err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Scan(&models)
if err != nil {
if err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Scan(&models); err != nil {
return
}
@ -310,10 +289,26 @@ func (s *sAdminRole) DataScopeEdit(ctx context.Context, in *adminin.DataScopeEdi
Where("id", in.Id).
Data(models).
Update()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
}
return nil
return
}
// treeList 树状列表
func (s *sAdminRole) treeList(pid int64, nodes []*entity.AdminRole) (list []*adminin.RoleTree) {
list = make([]*adminin.RoleTree, 0)
for _, v := range nodes {
if v.Pid == pid {
item := new(adminin.RoleTree)
item.AdminRole = *v
item.Label = v.Name
item.Value = v.Id
child := s.treeList(v.Id, nodes)
if len(child) > 0 {
item.Children = child
}
list = append(list, item)
}
}
return
}

View File

@ -20,11 +20,7 @@ import (
// AdminAuth 后台鉴权中间件
func (s *sMiddleware) AdminAuth(r *ghttp.Request) {
var (
ctx = r.Context()
)
var ctx = r.Context()
// 替换掉模块前缀
routerPrefix := g.Cfg().MustGet(ctx, "router.admin.prefix", "/admin")
path := gstr.Replace(r.URL.Path, routerPrefix.String(), "", 1)

View File

@ -9,14 +9,12 @@ package sys
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"hotgo/utility/tree"
"hotgo/utility/validate"
)
@ -157,31 +155,39 @@ func (s *sSysCronGroup) List(ctx context.Context, in sysin.CronGroupListInp) (li
}
// Select 选项
func (s *sSysCronGroup) Select(ctx context.Context, in sysin.CronGroupSelectInp) (list sysin.CronGroupSelectModel, err error) {
func (s *sSysCronGroup) Select(ctx context.Context, in sysin.CronGroupSelectInp) (res *sysin.CronGroupSelectModel, err error) {
var (
mod = dao.SysCronGroup.Ctx(ctx)
models []*entity.SysCronGroup
typeList []g.Map
mod = dao.SysCronGroup.Ctx(ctx)
models []*entity.SysCronGroup
)
if err = mod.Order("pid asc,sort asc").Scan(&models); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, err
return
}
for i := 0; i < len(models); i++ {
typeList = append(typeList, g.Map{
"index": models[i].Id,
"key": models[i].Id,
"label": models[i].Name,
"id": models[i].Id,
"pid": models[i].Pid,
"name": models[i].Name,
"sort": models[i].Sort,
"created_at": models[i].CreatedAt,
"status": models[i].Status,
})
}
return tree.GenTree(typeList), nil
res = new(sysin.CronGroupSelectModel)
res.List = s.treeList(0, models)
return
}
// treeList 树状列表
func (s *sSysCronGroup) treeList(pid int64, nodes []*entity.SysCronGroup) (list []*sysin.CronGroupTree) {
list = make([]*sysin.CronGroupTree, 0)
for _, v := range nodes {
if v.Pid == pid {
item := new(sysin.CronGroupTree)
item.SysCronGroup = *v
item.Label = v.Name
item.Value = v.Id
item.Key = v.Id
child := s.treeList(v.Id, nodes)
if len(child) > 0 {
item.Children = child
}
list = append(list, item)
}
}
return
}

View File

@ -9,14 +9,12 @@ package sys
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"hotgo/utility/tree"
)
type sSysDictType struct{}
@ -30,35 +28,19 @@ func init() {
}
// Tree 树
func (s *sSysDictType) Tree(ctx context.Context) (list []g.Map, err error) {
func (s *sSysDictType) Tree(ctx context.Context) (list []*sysin.DictTypeTree, err error) {
var (
mod = dao.SysDictType.Ctx(ctx)
models []*entity.SysDictType
)
if err = mod.Order("pid asc,sort asc").Scan(&models); err != nil {
if err = mod.Order("sort asc,id asc").Scan(&models); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, err
}
for i := 0; i < len(models); i++ {
list = append(list, g.Map{
"index": models[i].Id,
"key": models[i].Id,
"label": models[i].Name,
"id": models[i].Id,
"pid": models[i].Pid,
"name": models[i].Name,
"type": models[i].Type,
"sort": models[i].Sort,
"remark": models[i].Remark,
"status": models[i].Status,
"updated_at": models[i].UpdatedAt,
"created_at": models[i].CreatedAt,
})
}
return tree.GenTree(list), nil
list = s.treeList(0, models)
return
}
// Delete 删除
@ -141,42 +123,11 @@ func (s *sSysDictType) Edit(ctx context.Context, in sysin.DictTypeEditInp) (err
return nil
}
// Select 选项
func (s *sSysDictType) Select(ctx context.Context, in sysin.DictTypeSelectInp) (list sysin.DictTypeSelectModel, err error) {
var (
mod = dao.SysDictType.Ctx(ctx)
models []*entity.SysDictType
typeList []g.Map
)
if err = mod.Order("pid asc,sort asc").Scan(&models); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, err
}
for i := 0; i < len(models); i++ {
typeList = append(typeList, g.Map{
"index": models[i].Id,
"key": models[i].Id,
"label": models[i].Name,
"id": models[i].Id,
"pid": models[i].Pid,
"name": models[i].Name,
"sort": models[i].Sort,
"created_at": models[i].CreatedAt,
"status": models[i].Status,
})
}
return tree.GenTree(typeList), nil
}
// TreeSelect 获取类型关系树选项
func (s *sSysDictType) TreeSelect(ctx context.Context, in sysin.DictTreeSelectInp) (list sysin.DictTreeSelectModel, err error) {
func (s *sSysDictType) TreeSelect(ctx context.Context, in sysin.DictTreeSelectInp) (list []*sysin.DictTypeTree, err error) {
var (
mod = dao.SysDictType.Ctx(ctx)
models []*entity.SysDictType
typeList []g.Map
mod = dao.SysDictType.Ctx(ctx)
models []*entity.SysDictType
)
if err = mod.Order("pid asc,sort asc").Scan(&models); err != nil {
@ -184,26 +135,39 @@ func (s *sSysDictType) TreeSelect(ctx context.Context, in sysin.DictTreeSelectIn
return list, err
}
for i := 0; i < len(models); i++ {
typeList = append(typeList, g.Map{
"index": models[i].Id,
"key": models[i].Id,
"label": models[i].Name,
"id": models[i].Id,
"pid": models[i].Pid,
"name": models[i].Name,
"sort": models[i].Sort,
"created_at": models[i].CreatedAt,
"status": models[i].Status,
})
}
list = s.treeList(0, models)
maps := tree.GenTree(typeList)
for _, v := range maps {
for _, v := range list {
// 父类一律禁止选中
if _, ok := v["children"]; ok {
v["disabled"] = true
if len(v.Children) > 0 {
v.Disabled = true
for _, v2 := range v.Children {
if len(v2.Children) > 0 {
v2.Disabled = true
}
}
}
}
return tree.GenTree(typeList), nil
return
}
// treeList 树状列表
func (s *sSysDictType) treeList(pid int64, nodes []*entity.SysDictType) (list []*sysin.DictTypeTree) {
list = make([]*sysin.DictTypeTree, 0)
for _, v := range nodes {
if v.Pid == pid {
item := new(sysin.DictTypeTree)
item.SysDictType = *v
item.Label = v.Name
item.Value = v.Id
item.Key = v.Id
child := s.treeList(v.Id, nodes)
if len(child) > 0 {
item.Children = child
}
list = append(list, item)
}
}
return
}

View File

@ -166,6 +166,8 @@ type GenerateConfig struct {
type BuildAddonConfig struct {
SrcPath string `json:"srcPath"`
TemplatePath string `json:"templatePath"`
WebApiPath string `json:"webApiPath"`
WebViewsPath string `json:"webViewsPath"`
}
// CacheConfig 缓存配置

View File

@ -7,7 +7,6 @@
package adminin
import (
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/model/entity"
)
@ -57,34 +56,18 @@ type DeptListInp struct {
Code string
}
// DeptTreeDept
type DeptTreeDept struct {
// DeptTree
type DeptTree struct {
entity.AdminDept
Children []*DeptTreeDept `json:"children"`
Label string `json:"label" dc:"标签"`
Value int64 `json:"value" dc:"键值"`
Children []*DeptTree `json:"children"`
}
type DeptListModel []g.Map
// DeptListTreeInp 获取列表树
type DeptListTreeInp struct {
Name string
Code string
type DeptListModel struct {
List []*DeptTree `json:"list"`
}
// DeptListTreeDept 树
type DeptListTreeDept struct {
Id int64 `json:"id" `
Key int64 `json:"key" `
Pid int64 `json:"pid" `
Label string `json:"label"`
Title string `json:"title"`
Name string `json:"name"`
Type string `json:"type"`
Children []*DeptListTreeDept `json:"children"`
}
type DeptListTreeModel DeptListTreeDept
// DeptStatusInp 更新部门状态
type DeptStatusInp struct {
entity.AdminDept

View File

@ -7,6 +7,8 @@
package adminin
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/form"
@ -44,13 +46,13 @@ type MemberProfileInp struct {
Id int64
}
type MemberProfileModel struct {
PostGroup string `json:"postGroup" description:"岗位名称"`
RoleGroup string `json:"roleGroup" description:"角色名称"`
User *MemberViewModel `json:"member" description:"用户基本信息"`
SysDept *DeptViewModel `json:"sysDept" description:"部门信息"`
SysRoles []*RoleListModel `json:"sysRoles" description:"角色列表"`
PostIds int64 `json:"postIds" description:"当前岗位"`
RoleIds int64 `json:"roleIds" description:"当前角色"`
PostGroup string `json:"postGroup" dc:"岗位名称"`
RoleGroup string `json:"roleGroup" dc:"角色名称"`
User *MemberViewModel `json:"member" dc:"用户基本信息"`
SysDept *DeptViewModel `json:"sysDept" dc:"部门信息"`
SysRoles []*RoleListModel `json:"sysRoles" dc:"角色列表"`
PostIds int64 `json:"postIds" dc:"当前岗位"`
RoleIds int64 `json:"roleIds" dc:"当前角色"`
}
// MemberUpdateProfileInp 更新用户资料
@ -118,36 +120,48 @@ type MemberMaxSortModel struct {
// MemberEditInp 修改/新增管理员
type MemberEditInp struct {
Id int64 `json:"id" description:""`
RoleId int `json:"roleId" v:"required#角色不能为空" description:"角色ID"`
PostIds []int64 `json:"postIds" v:"required#岗位不能为空" description:"岗位ID"`
DeptId int64 `json:"deptId" v:"required#部门不能为空" description:"部门ID"`
Username string `json:"username" v:"required#账号不能为空" description:"帐号"`
Password string `json:"password" description:"密码"`
RealName string `json:"realName" description:"真实姓名"`
Avatar string `json:"avatar" description:"头像"`
Sex string `json:"sex" description:"性别"`
Qq string `json:"qq" description:"qq"`
Email string `json:"email" description:"邮箱"`
Birthday *gtime.Time `json:"birthday" description:"生日"`
ProvinceId int `json:"provinceId" description:"省"`
CityId int `json:"cityId" description:"城市"`
AreaId int `json:"areaId" description:"地区"`
Address string `json:"address" description:"默认地址"`
Mobile string `json:"mobile" description:"手机号码"`
Remark string `json:"remark" description:"备注"`
Status string `json:"status" description:"状态"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" description:"修改时间"`
Id int64 `json:"id" dc:""`
RoleId int `json:"roleId" v:"required#角色不能为空" dc:"角色ID"`
PostIds []int64 `json:"postIds" v:"required#岗位不能为空" dc:"岗位ID"`
DeptId int64 `json:"deptId" v:"required#部门不能为空" dc:"部门ID"`
Username string `json:"username" v:"required#账号不能为空" dc:"帐号"`
PasswordHash string `json:"passwordHash" dc:"密码hash"`
Password string `json:"password" dc:"密码"`
RealName string `json:"realName" dc:"真实姓名"`
Avatar string `json:"avatar" dc:"头像"`
Sex string `json:"sex" dc:"性别"`
Qq string `json:"qq" dc:"qq"`
Email string `json:"email" dc:"邮箱"`
Birthday *gtime.Time `json:"birthday" dc:"生日"`
ProvinceId int `json:"provinceId" dc:"省"`
CityId int `json:"cityId" dc:"城市"`
AreaId int `json:"areaId" dc:"地区"`
Address string `json:"address" dc:"默认地址"`
Mobile string `json:"mobile" dc:"手机号码"`
Remark string `json:"remark" dc:"备注"`
Status string `json:"status" dc:"状态"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
}
type MemberAddInp struct {
MemberEditInp
PasswordHash string `json:"passwordHash" description:"密码hash"`
Salt string `json:"salt" description:"密码盐"`
Pid int64 `json:"pid" description:"上级ID"`
Level int `json:"level" description:"等级"`
Tree string `json:"tree" description:"关系树"`
Salt string `json:"salt" dc:"密码盐"`
Pid int64 `json:"pid" dc:"上级ID"`
Level int `json:"level" dc:"等级"`
Tree string `json:"tree" dc:"关系树"`
}
func (in *MemberEditInp) Filter(ctx context.Context) (err error) {
if in.Password != "" {
if err := g.Validator().
Rules("length:6,16").
Messages("#新密码不能为空#新密码需在6~16之间").
Data(in.Password).Run(ctx); err != nil {
return err.Current()
}
}
return
}
type MemberEditModel struct{}
@ -165,8 +179,8 @@ type MemberViewInp struct {
type MemberViewModel struct {
entity.AdminMember
DeptName string `json:"deptName" description:"所属部门"`
RoleName string `json:"roleName" description:"所属角色"`
DeptName string `json:"deptName" dc:"所属部门"`
RoleName string `json:"roleName" dc:"所属角色"`
}
// MemberListInp 获取列表
@ -174,21 +188,21 @@ type MemberListInp struct {
form.PageReq
form.RangeDateReq
form.StatusReq
DeptId int `json:"deptId" dc:"部门ID"`
Mobile int `json:"mobile" dc:"手机号"`
DeptId int `json:"deptId" dc:"部门ID"`
Mobile int `json:"mobile" dc:"手机号"`
Username string `json:"username" dc:"用户名"`
RealName string `json:"realName" dc:"真实姓名"`
Name string `json:"name" dc:"岗位名称"`
Code string `json:"code" dc:"岗位编码"`
CreatedAt []int64 `json:"createdAt" dc:"创建时间"`
Name string `json:"name" dc:"岗位名称"`
Code string `json:"code" dc:"岗位编码"`
CreatedAt []int64 `json:"createdAt" dc:"创建时间"`
}
type MemberListModel struct {
entity.AdminMember
DeptName string `json:"deptName" description:"所属部门"`
RoleName string `json:"roleName" description:"所属角色"`
PostIds []int64 `json:"postIds" description:"岗位"`
DeptId int64 `json:"deptId" description:"部门ID"`
DeptName string `json:"deptName" dc:"所属部门"`
RoleName string `json:"roleName" dc:"所属角色"`
PostIds []int64 `json:"postIds" dc:"岗位"`
DeptId int64 `json:"deptId" dc:"部门ID"`
}
// MemberLoginInp 登录
@ -197,31 +211,31 @@ type MemberLoginInp struct {
Password string
}
type MemberLoginModel struct {
Id int64 `json:"id" description:"用户ID"`
Token string `json:"token" description:"登录token"`
Expires int64 `json:"expires" description:"登录有效期"`
Id int64 `json:"id" dc:"用户ID"`
Token string `json:"token" dc:"登录token"`
Expires int64 `json:"expires" dc:"登录有效期"`
}
type LoginMemberInfoModel struct {
Id int64 `json:"id" description:"用户ID"`
DeptName string `json:"deptName" description:"所属部门"`
RoleName string `json:"roleName" description:"所属角色"`
Permissions []string `json:"permissions" description:"角色信息"`
DeptId int64 `json:"-" description:"部门ID"`
RoleId int64 `json:"-" description:"角色ID"`
Username string `json:"username" description:"用户名"`
RealName string `json:"realName" description:"姓名"`
Avatar string `json:"avatar" description:"头像"`
Balance float64 `json:"balance" description:"余额"`
Sex int `json:"sex" description:"性别"`
Qq string `json:"qq" description:"qq"`
Email string `json:"email" description:"邮箱"`
Mobile string `json:"mobile" description:"手机号码"`
Birthday *gtime.Time `json:"birthday" description:"生日"`
CityId int64 `json:"cityId" description:"城市编码"`
Address string `json:"address" description:"联系地址"`
Cash *MemberCash `json:"cash" description:"收款信息"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
Id int64 `json:"id" dc:"用户ID"`
DeptName string `json:"deptName" dc:"所属部门"`
RoleName string `json:"roleName" dc:"所属角色"`
Permissions []string `json:"permissions" dc:"角色信息"`
DeptId int64 `json:"-" dc:"部门ID"`
RoleId int64 `json:"-" dc:"角色ID"`
Username string `json:"username" dc:"用户名"`
RealName string `json:"realName" dc:"姓名"`
Avatar string `json:"avatar" dc:"头像"`
Balance float64 `json:"balance" dc:"余额"`
Sex int `json:"sex" dc:"性别"`
Qq string `json:"qq" dc:"qq"`
Email string `json:"email" dc:"邮箱"`
Mobile string `json:"mobile" dc:"手机号码"`
Birthday *gtime.Time `json:"birthday" dc:"生日"`
CityId int64 `json:"cityId" dc:"城市编码"`
Address string `json:"address" dc:"联系地址"`
Cash *MemberCash `json:"cash" dc:"收款信息"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
*MemberLoginStatModel
}
@ -246,10 +260,10 @@ type MemberSelectInp struct {
}
type MemberSelectModel struct {
Value int64 `json:"value" dc:"用户ID"`
Label string `json:"label" dc:"真实姓名"`
Value int64 `json:"value" dc:"用户ID"`
Label string `json:"label" dc:"真实姓名"`
Username string `json:"username" dc:"用户名"`
Avatar string `json:"avatar" dc:"头像"`
Avatar string `json:"avatar" dc:"头像"`
}
// MemberLoginStatInp 用户登录统计

View File

@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package adminin
import (
@ -123,9 +122,7 @@ type MenuRouteMeta struct {
FrameSrc string `json:"frameSrc,omitempty" ` // 内联外部地址
Permissions string `json:"permissions,omitempty"` // 菜单包含权限集合,满足其中一个就会显示
Affix bool `json:"affix,omitempty"` // 是否固定 设置为 true 之后 多页签不可删除
// 自定义
Type int `json:"type"` // 菜单类型
Type int `json:"type"` // 菜单类型
}
type MenuRoute struct {

View File

@ -10,6 +10,7 @@ import (
"hotgo/internal/model"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/form"
"sort"
)
// RoleListInp 获取列表
@ -17,10 +18,27 @@ type RoleListInp struct {
form.PageReq
}
type RoleListModel struct {
type RoleTree struct {
entity.AdminRole
Label string `json:"label" dc:"标签"`
Value int64 `json:"value" dc:"键值"`
Label string `json:"label" dc:"标签"`
Value int64 `json:"value" dc:"键值"`
Children []*RoleTree `json:"children" dc:"子级"`
}
type RoleListModel struct {
List []*RoleTree `json:"list"`
}
func Sort(v []*RoleTree) {
sort.SliceStable(v, func(i, j int) bool {
if v[i].Sort < v[j].Sort {
return true
}
if v[i].Sort > v[j].Sort {
return false
}
return v[i].Id < v[j].Id
})
}
// RoleMemberListInp 查询列表
@ -28,15 +46,15 @@ type RoleMemberListInp struct {
form.PageReq
form.RangeDateReq
form.StatusReq
Role int `json:"role" description:"角色ID"`
DeptId int `json:"deptId" description:"部门ID"`
Mobile int `json:"mobile" description:"手机号"`
Username string `json:"username" description:"用户名"`
Realname string `json:"realname" description:"真实姓名"`
StartTime string `json:"start_time" description:"开始时间"`
EndTime string `json:"end_time" description:"结束时间"`
Name string `json:"name" description:"岗位名称"`
Code string `json:"code" description:"岗位编码"`
Role int `json:"role" dc:"角色ID"`
DeptId int `json:"deptId" dc:"部门ID"`
Mobile int `json:"mobile" dc:"手机号"`
Username string `json:"username" dc:"用户名"`
RealName string `json:"realName" dc:"真实姓名"`
StartTime string `json:"start_time" dc:"开始时间"`
EndTime string `json:"end_time" dc:"结束时间"`
Name string `json:"name" dc:"岗位名称"`
Code string `json:"code" dc:"岗位编码"`
}
type RoleMemberListModel []*MemberListModel
@ -46,12 +64,12 @@ type MenuRoleListInp struct {
RoleId int64
}
type MenuRoleListModel struct {
Menus []*model.LabelTreeMenu `json:"menus" description:"菜单列表"`
CheckedKeys []int64 `json:"checkedKeys" description:"选择的菜单ID"`
Menus []*model.LabelTreeMenu `json:"menus" dc:"菜单列表"`
CheckedKeys []int64 `json:"checkedKeys" dc:"选择的菜单ID"`
}
type DataScopeEditInp struct {
Id int64 `json:"id" v:"required" dc:"角色ID"`
Id int64 `json:"id" v:"required" dc:"角色ID"`
DataScope int `json:"dataScope" v:"required" dc:"数据范围"`
CustomDept []int64 `json:"customDept" dc:"自定义部门权限"`
CustomDept []int64 `json:"customDept" dc:"自定义部门权限"`
}

View File

@ -7,7 +7,6 @@
package sysin
import (
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/form"
)
@ -64,4 +63,15 @@ type CronGroupStatusModel struct{}
type CronGroupSelectInp struct {
}
type CronGroupSelectModel []g.Map
type CronGroupSelectModel struct {
List []*CronGroupTree `json:"list"`
}
type CronGroupTree struct {
entity.SysCronGroup
Disabled bool `json:"disabled" dc:"是否禁用"`
Label string `json:"label" dc:"标签"`
Value int64 `json:"value" dc:"键值"`
Key int64 `json:"key" dc:"键名"`
Children []*CronGroupTree `json:"children" dc:"子级"`
}

View File

@ -23,14 +23,17 @@ type DictTypeDeleteInp struct {
}
type DictTypeDeleteModel struct{}
// DictTypeSelectInp 获取类型选项
type DictTypeSelectInp struct {
}
type DictTypeSelectModel []g.Map
// DictTreeSelectInp 获取类型关系树选项
type DictTreeSelectInp struct {
}
type DictTreeSelectModel []g.Map
type DictTypeTree struct {
entity.SysDictType
Disabled bool `json:"disabled" dc:"是否禁用"`
Label string `json:"label" dc:"标签"`
Value int64 `json:"value" dc:"键值"`
Key int64 `json:"key" dc:"键名"`
Children []*DictTypeTree `json:"children" dc:"子级"`
}

View File

@ -83,11 +83,11 @@ type GenCodesSelectsModel struct {
LinkMode form.Selects `json:"linkMode" dc:"关联表方式"`
BuildMeth form.Selects `json:"buildMeth" dc:"生成方式"`
// 字段表格选项
FormMode form.Selects `json:"formMode" dc:"表单组件"`
FormRole form.Selects `json:"formRole" dc:"表单验证"`
DictMode DictTreeSelectModel `json:"dictMode" dc:"字典类型"`
WhereMode form.Selects `json:"whereMode" dc:"查询条件"`
Addons form.Selects `json:"addons" dc:"插件选项"`
FormMode form.Selects `json:"formMode" dc:"表单组件"`
FormRole form.Selects `json:"formRole" dc:"表单验证"`
DictMode []*DictTypeTree `json:"dictMode" dc:"字典类型"`
WhereMode form.Selects `json:"whereMode" dc:"查询条件"`
Addons form.Selects `json:"addons" dc:"插件选项"`
}
type GenTypeSelects []*GenTypeSelect

View File

@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package model
import (
@ -12,7 +11,7 @@ import (
type MonitorData struct {
// STartTime 启动时间
STartTime *gtime.Time
STartTime int64
// 内网IP
IntranetIP string
// 公网IP

View File

@ -1,70 +0,0 @@
// Package queues
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package queues
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/library/queue"
)
type jobStrategy interface {
getTopic() string
handle(ctx context.Context, mqMsg queue.MqMsg) (err error)
}
var jobList []jobStrategy
func Run(ctx context.Context) {
for _, job := range uniqueJob(jobList) {
go func(job jobStrategy) {
listen(ctx, job)
}(job)
}
}
func listen(ctx context.Context, job jobStrategy) {
var (
topic = job.getTopic()
consumer, err = queue.InstanceConsumer()
)
if err != nil {
g.Log().Fatalf(ctx, "InstanceConsumer %s err:%+v", topic, err)
return
}
// 访问日志
if listenErr := consumer.ListenReceiveMsgDo(topic, func(mqMsg queue.MqMsg) {
err = job.handle(ctx, mqMsg)
if err != nil {
// 遇到错误,重新加入到队列
//queue.Push(topic, mqMsg.Body)
}
// 记录队列日志
queue.ConsumerLog(ctx, topic, mqMsg, err)
}); listenErr != nil {
g.Log().Fatalf(ctx, "队列:%s 监听失败, err:%+v", topic, listenErr)
}
}
// uniqueJob 去重
func uniqueJob(languages []jobStrategy) []jobStrategy {
result := make([]jobStrategy, 0, len(languages))
temp := map[jobStrategy]struct{}{}
for _, item := range languages {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package queues
import (
@ -16,7 +15,7 @@ import (
)
func init() {
jobList = append(jobList, LoginLog)
queue.RegisterConsumer(LoginLog)
}
// LoginLog 登录日志
@ -24,13 +23,13 @@ var LoginLog = &qLoginLog{}
type qLoginLog struct{}
// getTopic 主题
func (q *qLoginLog) getTopic() string {
// GetTopic 主题
func (q *qLoginLog) GetTopic() string {
return consts.QueueLoginLogTopic
}
// handle 处理消息
func (q *qLoginLog) handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
// Handle 处理消息
func (q *qLoginLog) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
var data entity.SysLoginLog
if err = json.Unmarshal(mqMsg.Body, &data); err != nil {
return err

View File

@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package queues
import (
@ -16,7 +15,7 @@ import (
)
func init() {
jobList = append(jobList, ServeLog)
queue.RegisterConsumer(ServeLog)
}
// ServeLog 登录日志
@ -24,13 +23,13 @@ var ServeLog = &qServeLog{}
type qServeLog struct{}
// getTopic 主题
func (q *qServeLog) getTopic() string {
// GetTopic 主题
func (q *qServeLog) GetTopic() string {
return consts.QueueServeLogTopic
}
// handle 处理消息
func (q *qServeLog) handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
// Handle 处理消息
func (q *qServeLog) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
var data entity.SysServeLog
if err = json.Unmarshal(mqMsg.Body, &data); err != nil {
return err

View File

@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package queues
import (
@ -16,7 +15,7 @@ import (
)
func init() {
jobList = append(jobList, SysLog)
queue.RegisterConsumer(SysLog)
}
// SysLog 系统日志
@ -24,13 +23,13 @@ var SysLog = &qSysLog{}
type qSysLog struct{}
// getTopic 主题
func (q *qSysLog) getTopic() string {
// GetTopic 主题
func (q *qSysLog) GetTopic() string {
return consts.QueueLogTopic
}
// handle 处理消息
func (q *qSysLog) handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
// Handle 处理消息
func (q *qSysLog) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
var data entity.SysLog
if err = json.Unmarshal(mqMsg.Body, &data); err != nil {
return err

View File

@ -38,11 +38,11 @@ func WebSocket(ctx context.Context, group *ghttp.RouterGroup) {
// 注册消息路由
websocket.RegisterMsg(websocket.EventHandlers{
"ping": common.Site.Ping, // 心跳
"join": common.Site.Join, // 加入组
"quit": common.Site.Quit, // 退出组
"adminMonitorTrends": admin.Monitor.Trends, // 后台监控,动态数据
"adminMonitorRunInfo": admin.Monitor.RunInfo, // 后台监控,运行信息
"ping": common.Site.Ping, // 心跳
"join": common.Site.Join, // 加入组
"quit": common.Site.Quit, // 退出组
"admin/monitor/trends": admin.Monitor.Trends, // 后台监控,动态数据
"admin/monitor/runInfo": admin.Monitor.RunInfo, // 后台监控,运行信息
})
}

View File

@ -15,14 +15,9 @@ import (
"hotgo/internal/model/input/form"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
type (
IAdminMonitor interface {
StartMonitor(ctx context.Context)
GetMeta(ctx context.Context) *model.MonitorData
}
IAdminNotice interface {
Model(ctx context.Context, option ...*handler.Option) *gdb.Model
Delete(ctx context.Context, in adminin.NoticeDeleteInp) error
@ -50,7 +45,7 @@ type (
}
IAdminRole interface {
Verify(ctx context.Context, path, method string) bool
List(ctx context.Context, in adminin.RoleListInp) (list []g.Map, totalCount int, err error)
List(ctx context.Context, in adminin.RoleListInp) (res *adminin.RoleListModel, totalCount int, err error)
GetName(ctx context.Context, RoleId int64) (name string, err error)
GetMemberList(ctx context.Context, RoleId int64) (list []*adminin.RoleListModel, err error)
GetPermissions(ctx context.Context, reqInfo *role.GetPermissionsReq) (MenuIds []int64, err error)
@ -61,14 +56,13 @@ type (
DataScopeEdit(ctx context.Context, in *adminin.DataScopeEditInp) (err error)
}
IAdminDept interface {
NameUnique(ctx context.Context, in adminin.DeptNameUniqueInp) (*adminin.DeptNameUniqueModel, error)
NameUnique(ctx context.Context, in adminin.DeptNameUniqueInp) (res *adminin.DeptNameUniqueModel, err error)
Delete(ctx context.Context, in adminin.DeptDeleteInp) (err error)
Edit(ctx context.Context, in adminin.DeptEditInp) (err error)
Status(ctx context.Context, in adminin.DeptStatusInp) (err error)
MaxSort(ctx context.Context, in adminin.DeptMaxSortInp) (*adminin.DeptMaxSortModel, error)
MaxSort(ctx context.Context, in adminin.DeptMaxSortInp) (res *adminin.DeptMaxSortModel, err error)
View(ctx context.Context, in adminin.DeptViewInp) (res *adminin.DeptViewModel, err error)
List(ctx context.Context, in adminin.DeptListInp) (list adminin.DeptListModel, err error)
ListTree(ctx context.Context, in adminin.DeptListTreeInp) (list []*adminin.DeptListTreeModel, err error)
List(ctx context.Context, in adminin.DeptListInp) (res *adminin.DeptListModel, err error)
GetName(ctx context.Context, id int64) (name string, err error)
}
IAdminMember interface {
@ -97,8 +91,8 @@ type (
MemberLoginStat(ctx context.Context, in adminin.MemberLoginStatInp) (res *adminin.MemberLoginStatModel, err error)
}
IAdminMemberPost interface {
UpdatePostIds(ctx context.Context, member_id int64, post_ids []int64) (err error)
GetMemberByIds(ctx context.Context, member_id int64) (post_ids []int64, err error)
UpdatePostIds(ctx context.Context, memberId int64, postIds []int64) (err error)
GetMemberByIds(ctx context.Context, memberId int64) (postIds []int64, err error)
}
IAdminMenu interface {
RoleList(ctx context.Context, in adminin.MenuRoleListInp) (*adminin.MenuRoleListModel, error)
@ -113,9 +107,14 @@ type (
GetMenuList(ctx context.Context, memberId int64) (lists role.DynamicRes, err error)
LoginPermissions(ctx context.Context, memberId int64) (lists adminin.MemberLoginPermissions, err error)
}
IAdminMonitor interface {
StartMonitor(ctx context.Context)
GetMeta(ctx context.Context) *model.MonitorData
}
)
var (
localAdminPost IAdminPost
localAdminRole IAdminRole
localAdminDept IAdminDept
localAdminMember IAdminMember
@ -123,7 +122,6 @@ var (
localAdminMenu IAdminMenu
localAdminMonitor IAdminMonitor
localAdminNotice IAdminNotice
localAdminPost IAdminPost
)
func AdminDept() IAdminDept {

View File

@ -17,6 +17,20 @@ import (
)
type (
ISysAddonsConfig interface {
GetConfigByGroup(ctx context.Context, in sysin.GetAddonsConfigInp) (res *sysin.GetAddonsConfigModel, err error)
ConversionType(ctx context.Context, models *entity.SysAddonsConfig) (value interface{}, err error)
UpdateConfigByGroup(ctx context.Context, in sysin.UpdateAddonsConfigInp) error
}
ISysAttachment interface {
Delete(ctx context.Context, in sysin.AttachmentDeleteInp) error
Edit(ctx context.Context, in sysin.AttachmentEditInp) (err error)
Status(ctx context.Context, in sysin.AttachmentStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.AttachmentMaxSortInp) (*sysin.AttachmentMaxSortModel, error)
View(ctx context.Context, in sysin.AttachmentViewInp) (res *sysin.AttachmentViewModel, err error)
List(ctx context.Context, in sysin.AttachmentListInp) (list []*sysin.AttachmentListModel, totalCount int, err error)
Add(ctx context.Context, meta *sysin.UploadFileMeta, fullPath, drive string) (data *entity.SysAttachment, err error)
}
ISysBlacklist interface {
Delete(ctx context.Context, in sysin.BlacklistDeleteInp) (err error)
Edit(ctx context.Context, in sysin.BlacklistEditInp) (err error)
@ -27,12 +41,6 @@ type (
VariableLoad(ctx context.Context, err error)
Load(ctx context.Context)
}
ISysDictData interface {
Delete(ctx context.Context, in sysin.DictDataDeleteInp) error
Edit(ctx context.Context, in sysin.DictDataEditInp) (err error)
List(ctx context.Context, in sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int, err error)
Select(ctx context.Context, in sysin.DataSelectInp) (list sysin.DataSelectModel, err error)
}
ISysEmsLog interface {
Delete(ctx context.Context, in sysin.EmsLogDeleteInp) error
Edit(ctx context.Context, in sysin.EmsLogEditInp) (err error)
@ -44,60 +52,6 @@ type (
AllowSend(ctx context.Context, models *entity.SysEmsLog, config *model.EmailConfig) (err error)
VerifyCode(ctx context.Context, in sysin.VerifyEmsCodeInp) (err error)
}
ISysLog interface {
Export(ctx context.Context, in sysin.LogListInp) (err error)
RealWrite(ctx context.Context, commonLog entity.SysLog) (err error)
AutoLog(ctx context.Context) error
AnalysisLog(ctx context.Context) entity.SysLog
View(ctx context.Context, in sysin.LogViewInp) (res *sysin.LogViewModel, err error)
Delete(ctx context.Context, in sysin.LogDeleteInp) (err error)
List(ctx context.Context, in sysin.LogListInp) (list []*sysin.LogListModel, totalCount int, err error)
}
ISysConfig interface {
GetLoadCache(ctx context.Context) (conf *model.CacheConfig, err error)
GetLoadGenerate(ctx context.Context) (conf *model.GenerateConfig, err error)
GetSms(ctx context.Context) (conf *model.SmsConfig, err error)
GetGeo(ctx context.Context) (conf *model.GeoConfig, err error)
GetUpload(ctx context.Context) (conf *model.UploadConfig, err error)
GetSmtp(ctx context.Context) (conf *model.EmailConfig, err error)
GetBasic(ctx context.Context) (conf *model.BasicConfig, err error)
GetLoadSSL(ctx context.Context) (conf *model.SSLConfig, err error)
GetLoadLog(ctx context.Context) (conf *model.LogConfig, err error)
GetLoadServeLog(ctx context.Context) (conf *model.ServeLogConfig, err error)
GetConfigByGroup(ctx context.Context, in sysin.GetConfigInp) (*sysin.GetConfigModel, error)
ConversionType(ctx context.Context, models *entity.SysConfig) (value interface{}, err error)
UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error
}
ISysCron interface {
StartCron(ctx context.Context)
Delete(ctx context.Context, in sysin.CronDeleteInp) (err error)
Edit(ctx context.Context, in sysin.CronEditInp) (err error)
Status(ctx context.Context, in sysin.CronStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.CronMaxSortInp) (res *sysin.CronMaxSortModel, err error)
View(ctx context.Context, in sysin.CronViewInp) (res *sysin.CronViewModel, err error)
List(ctx context.Context, in sysin.CronListInp) (list []*sysin.CronListModel, totalCount int, err error)
OnlineExec(ctx context.Context, in sysin.OnlineExecInp) (err error)
}
ISysCronGroup interface {
Delete(ctx context.Context, in sysin.CronGroupDeleteInp) error
Edit(ctx context.Context, in sysin.CronGroupEditInp) (err error)
Status(ctx context.Context, in sysin.CronGroupStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.CronGroupMaxSortInp) (*sysin.CronGroupMaxSortModel, error)
View(ctx context.Context, in sysin.CronGroupViewInp) (res *sysin.CronGroupViewModel, err error)
List(ctx context.Context, in sysin.CronGroupListInp) (list []*sysin.CronGroupListModel, totalCount int, err error)
Select(ctx context.Context, in sysin.CronGroupSelectInp) (list sysin.CronGroupSelectModel, err error)
}
ISysCurdDemo interface {
Model(ctx context.Context, option ...*handler.Option) *gdb.Model
List(ctx context.Context, in sysin.CurdDemoListInp) (list []*sysin.CurdDemoListModel, totalCount int, err error)
Export(ctx context.Context, in sysin.CurdDemoListInp) (err error)
Edit(ctx context.Context, in sysin.CurdDemoEditInp) (err error)
Delete(ctx context.Context, in sysin.CurdDemoDeleteInp) (err error)
MaxSort(ctx context.Context, in sysin.CurdDemoMaxSortInp) (res *sysin.CurdDemoMaxSortModel, err error)
View(ctx context.Context, in sysin.CurdDemoViewInp) (res *sysin.CurdDemoViewModel, err error)
Status(ctx context.Context, in sysin.CurdDemoStatusInp) (err error)
Switch(ctx context.Context, in sysin.CurdDemoSwitchInp) (err error)
}
ISysGenCodes interface {
Delete(ctx context.Context, in sysin.GenCodesDeleteInp) error
Edit(ctx context.Context, in sysin.GenCodesEditInp) (res *sysin.GenCodesEditModel, err error)
@ -112,56 +66,6 @@ type (
Preview(ctx context.Context, in sysin.GenCodesPreviewInp) (res *sysin.GenCodesPreviewModel, err error)
Build(ctx context.Context, in sysin.GenCodesBuildInp) (err error)
}
ISysAddons interface {
List(ctx context.Context, in sysin.AddonsListInp) (list []*sysin.AddonsListModel, totalCount int, err error)
Selects(ctx context.Context, in sysin.AddonsSelectsInp) (res *sysin.AddonsSelectsModel, err error)
Build(ctx context.Context, in sysin.AddonsBuildInp) (err error)
Install(ctx context.Context, in sysin.AddonsInstallInp) (err error)
Upgrade(ctx context.Context, in sysin.AddonsUpgradeInp) (err error)
UnInstall(ctx context.Context, in sysin.AddonsUnInstallInp) (err error)
}
ISysAddonsConfig interface {
GetConfigByGroup(ctx context.Context, in sysin.GetAddonsConfigInp) (res *sysin.GetAddonsConfigModel, err error)
ConversionType(ctx context.Context, models *entity.SysAddonsConfig) (value interface{}, err error)
UpdateConfigByGroup(ctx context.Context, in sysin.UpdateAddonsConfigInp) error
}
ISysDictType interface {
Tree(ctx context.Context) (list []g.Map, err error)
Delete(ctx context.Context, in sysin.DictTypeDeleteInp) error
Edit(ctx context.Context, in sysin.DictTypeEditInp) (err error)
Select(ctx context.Context, in sysin.DictTypeSelectInp) (list sysin.DictTypeSelectModel, err error)
TreeSelect(ctx context.Context, in sysin.DictTreeSelectInp) (list sysin.DictTreeSelectModel, err error)
}
ISysLoginLog interface {
Model(ctx context.Context) *gdb.Model
List(ctx context.Context, in sysin.LoginLogListInp) (list []*sysin.LoginLogListModel, totalCount int, err error)
Export(ctx context.Context, in sysin.LoginLogListInp) (err error)
Delete(ctx context.Context, in sysin.LoginLogDeleteInp) (err error)
View(ctx context.Context, in sysin.LoginLogViewInp) (res *sysin.LoginLogViewModel, err error)
Push(ctx context.Context, in sysin.LoginLogPushInp)
RealWrite(ctx context.Context, models entity.SysLoginLog) (err error)
}
ISysAttachment interface {
Delete(ctx context.Context, in sysin.AttachmentDeleteInp) error
Edit(ctx context.Context, in sysin.AttachmentEditInp) (err error)
Status(ctx context.Context, in sysin.AttachmentStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.AttachmentMaxSortInp) (*sysin.AttachmentMaxSortModel, error)
View(ctx context.Context, in sysin.AttachmentViewInp) (res *sysin.AttachmentViewModel, err error)
List(ctx context.Context, in sysin.AttachmentListInp) (list []*sysin.AttachmentListModel, totalCount int, err error)
Add(ctx context.Context, meta *sysin.UploadFileMeta, fullPath, drive string) (data *entity.SysAttachment, err error)
}
ISysProvinces interface {
Tree(ctx context.Context) (list []g.Map, err error)
Delete(ctx context.Context, in sysin.ProvincesDeleteInp) error
Edit(ctx context.Context, in sysin.ProvincesEditInp) (err error)
Status(ctx context.Context, in sysin.ProvincesStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.ProvincesMaxSortInp) (res *sysin.ProvincesMaxSortModel, err error)
View(ctx context.Context, in sysin.ProvincesViewInp) (res *sysin.ProvincesViewModel, err error)
List(ctx context.Context, in sysin.ProvincesListInp) (list []*sysin.ProvincesListModel, totalCount int, err error)
ChildrenList(ctx context.Context, in sysin.ProvincesChildrenListInp) (list []*sysin.ProvincesChildrenListModel, totalCount int, err error)
UniqueId(ctx context.Context, in sysin.ProvincesUniqueIdInp) (res *sysin.ProvincesUniqueIdModel, err error)
Select(ctx context.Context, in sysin.ProvincesSelectInp) (res *sysin.ProvincesSelectModel, err error)
}
ISysServeLog interface {
Model(ctx context.Context) *gdb.Model
List(ctx context.Context, in sysin.ServeLogListInp) (list []*sysin.ServeLogListModel, totalCount int, err error)
@ -182,28 +86,178 @@ type (
AllowSend(ctx context.Context, models *entity.SysSmsLog, config *model.SmsConfig) (err error)
VerifyCode(ctx context.Context, in sysin.VerifyCodeInp) (err error)
}
ISysAddons interface {
List(ctx context.Context, in sysin.AddonsListInp) (list []*sysin.AddonsListModel, totalCount int, err error)
Selects(ctx context.Context, in sysin.AddonsSelectsInp) (res *sysin.AddonsSelectsModel, err error)
Build(ctx context.Context, in sysin.AddonsBuildInp) (err error)
Install(ctx context.Context, in sysin.AddonsInstallInp) (err error)
Upgrade(ctx context.Context, in sysin.AddonsUpgradeInp) (err error)
UnInstall(ctx context.Context, in sysin.AddonsUnInstallInp) (err error)
}
ISysConfig interface {
GetLoadCache(ctx context.Context) (conf *model.CacheConfig, err error)
GetLoadGenerate(ctx context.Context) (conf *model.GenerateConfig, err error)
GetSms(ctx context.Context) (conf *model.SmsConfig, err error)
GetGeo(ctx context.Context) (conf *model.GeoConfig, err error)
GetUpload(ctx context.Context) (conf *model.UploadConfig, err error)
GetSmtp(ctx context.Context) (conf *model.EmailConfig, err error)
GetBasic(ctx context.Context) (conf *model.BasicConfig, err error)
GetLoadSSL(ctx context.Context) (conf *model.SSLConfig, err error)
GetLoadLog(ctx context.Context) (conf *model.LogConfig, err error)
GetLoadServeLog(ctx context.Context) (conf *model.ServeLogConfig, err error)
GetConfigByGroup(ctx context.Context, in sysin.GetConfigInp) (*sysin.GetConfigModel, error)
ConversionType(ctx context.Context, models *entity.SysConfig) (value interface{}, err error)
UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error
}
ISysLog interface {
Export(ctx context.Context, in sysin.LogListInp) (err error)
RealWrite(ctx context.Context, commonLog entity.SysLog) (err error)
AutoLog(ctx context.Context) error
AnalysisLog(ctx context.Context) entity.SysLog
View(ctx context.Context, in sysin.LogViewInp) (res *sysin.LogViewModel, err error)
Delete(ctx context.Context, in sysin.LogDeleteInp) (err error)
List(ctx context.Context, in sysin.LogListInp) (list []*sysin.LogListModel, totalCount int, err error)
}
ISysLoginLog interface {
Model(ctx context.Context) *gdb.Model
List(ctx context.Context, in sysin.LoginLogListInp) (list []*sysin.LoginLogListModel, totalCount int, err error)
Export(ctx context.Context, in sysin.LoginLogListInp) (err error)
Delete(ctx context.Context, in sysin.LoginLogDeleteInp) (err error)
View(ctx context.Context, in sysin.LoginLogViewInp) (res *sysin.LoginLogViewModel, err error)
Push(ctx context.Context, in sysin.LoginLogPushInp)
RealWrite(ctx context.Context, models entity.SysLoginLog) (err error)
}
ISysCronGroup interface {
Delete(ctx context.Context, in sysin.CronGroupDeleteInp) error
Edit(ctx context.Context, in sysin.CronGroupEditInp) (err error)
Status(ctx context.Context, in sysin.CronGroupStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.CronGroupMaxSortInp) (*sysin.CronGroupMaxSortModel, error)
View(ctx context.Context, in sysin.CronGroupViewInp) (res *sysin.CronGroupViewModel, err error)
List(ctx context.Context, in sysin.CronGroupListInp) (list []*sysin.CronGroupListModel, totalCount int, err error)
Select(ctx context.Context, in sysin.CronGroupSelectInp) (res *sysin.CronGroupSelectModel, err error)
}
ISysCurdDemo interface {
Model(ctx context.Context, option ...*handler.Option) *gdb.Model
List(ctx context.Context, in sysin.CurdDemoListInp) (list []*sysin.CurdDemoListModel, totalCount int, err error)
Export(ctx context.Context, in sysin.CurdDemoListInp) (err error)
Edit(ctx context.Context, in sysin.CurdDemoEditInp) (err error)
Delete(ctx context.Context, in sysin.CurdDemoDeleteInp) (err error)
MaxSort(ctx context.Context, in sysin.CurdDemoMaxSortInp) (res *sysin.CurdDemoMaxSortModel, err error)
View(ctx context.Context, in sysin.CurdDemoViewInp) (res *sysin.CurdDemoViewModel, err error)
Status(ctx context.Context, in sysin.CurdDemoStatusInp) (err error)
Switch(ctx context.Context, in sysin.CurdDemoSwitchInp) (err error)
}
ISysDictData interface {
Delete(ctx context.Context, in sysin.DictDataDeleteInp) error
Edit(ctx context.Context, in sysin.DictDataEditInp) (err error)
List(ctx context.Context, in sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int, err error)
Select(ctx context.Context, in sysin.DataSelectInp) (list sysin.DataSelectModel, err error)
}
ISysDictType interface {
Tree(ctx context.Context) (list []*sysin.DictTypeTree, err error)
Delete(ctx context.Context, in sysin.DictTypeDeleteInp) error
Edit(ctx context.Context, in sysin.DictTypeEditInp) (err error)
TreeSelect(ctx context.Context, in sysin.DictTreeSelectInp) (list []*sysin.DictTypeTree, err error)
}
ISysProvinces interface {
Tree(ctx context.Context) (list []g.Map, err error)
Delete(ctx context.Context, in sysin.ProvincesDeleteInp) error
Edit(ctx context.Context, in sysin.ProvincesEditInp) (err error)
Status(ctx context.Context, in sysin.ProvincesStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.ProvincesMaxSortInp) (res *sysin.ProvincesMaxSortModel, err error)
View(ctx context.Context, in sysin.ProvincesViewInp) (res *sysin.ProvincesViewModel, err error)
List(ctx context.Context, in sysin.ProvincesListInp) (list []*sysin.ProvincesListModel, totalCount int, err error)
ChildrenList(ctx context.Context, in sysin.ProvincesChildrenListInp) (list []*sysin.ProvincesChildrenListModel, totalCount int, err error)
UniqueId(ctx context.Context, in sysin.ProvincesUniqueIdInp) (res *sysin.ProvincesUniqueIdModel, err error)
Select(ctx context.Context, in sysin.ProvincesSelectInp) (res *sysin.ProvincesSelectModel, err error)
}
ISysCron interface {
StartCron(ctx context.Context)
Delete(ctx context.Context, in sysin.CronDeleteInp) (err error)
Edit(ctx context.Context, in sysin.CronEditInp) (err error)
Status(ctx context.Context, in sysin.CronStatusInp) (err error)
MaxSort(ctx context.Context, in sysin.CronMaxSortInp) (res *sysin.CronMaxSortModel, err error)
View(ctx context.Context, in sysin.CronViewInp) (res *sysin.CronViewModel, err error)
List(ctx context.Context, in sysin.CronListInp) (list []*sysin.CronListModel, totalCount int, err error)
OnlineExec(ctx context.Context, in sysin.OnlineExecInp) (err error)
}
)
var (
localSysCron ISysCron
localSysCronGroup ISysCronGroup
localSysCurdDemo ISysCurdDemo
localSysAttachment ISysAttachment
localSysBlacklist ISysBlacklist
localSysEmsLog ISysEmsLog
localSysGenCodes ISysGenCodes
localSysLog ISysLog
localSysConfig ISysConfig
localSysAddonsConfig ISysAddonsConfig
localSysDictType ISysDictType
localSysLoginLog ISysLoginLog
localSysAddons ISysAddons
localSysProvinces ISysProvinces
localSysServeLog ISysServeLog
localSysSmsLog ISysSmsLog
localSysAttachment ISysAttachment
localSysAddonsConfig ISysAddonsConfig
localSysConfig ISysConfig
localSysLog ISysLog
localSysLoginLog ISysLoginLog
localSysAddons ISysAddons
localSysCurdDemo ISysCurdDemo
localSysDictData ISysDictData
localSysEmsLog ISysEmsLog
localSysBlacklist ISysBlacklist
localSysDictType ISysDictType
localSysProvinces ISysProvinces
localSysCronGroup ISysCronGroup
)
func SysGenCodes() ISysGenCodes {
if localSysGenCodes == nil {
panic("implement not found for interface ISysGenCodes, forgot register?")
}
return localSysGenCodes
}
func RegisterSysGenCodes(i ISysGenCodes) {
localSysGenCodes = i
}
func SysServeLog() ISysServeLog {
if localSysServeLog == nil {
panic("implement not found for interface ISysServeLog, forgot register?")
}
return localSysServeLog
}
func RegisterSysServeLog(i ISysServeLog) {
localSysServeLog = i
}
func SysSmsLog() ISysSmsLog {
if localSysSmsLog == nil {
panic("implement not found for interface ISysSmsLog, forgot register?")
}
return localSysSmsLog
}
func RegisterSysSmsLog(i ISysSmsLog) {
localSysSmsLog = i
}
func SysAddonsConfig() ISysAddonsConfig {
if localSysAddonsConfig == nil {
panic("implement not found for interface ISysAddonsConfig, forgot register?")
}
return localSysAddonsConfig
}
func RegisterSysAddonsConfig(i ISysAddonsConfig) {
localSysAddonsConfig = i
}
func SysAttachment() ISysAttachment {
if localSysAttachment == nil {
panic("implement not found for interface ISysAttachment, forgot register?")
}
return localSysAttachment
}
func RegisterSysAttachment(i ISysAttachment) {
localSysAttachment = i
}
func SysBlacklist() ISysBlacklist {
if localSysBlacklist == nil {
panic("implement not found for interface ISysBlacklist, forgot register?")
@ -215,17 +269,6 @@ func RegisterSysBlacklist(i ISysBlacklist) {
localSysBlacklist = i
}
func SysDictData() ISysDictData {
if localSysDictData == nil {
panic("implement not found for interface ISysDictData, forgot register?")
}
return localSysDictData
}
func RegisterSysDictData(i ISysDictData) {
localSysDictData = i
}
func SysEmsLog() ISysEmsLog {
if localSysEmsLog == nil {
panic("implement not found for interface ISysEmsLog, forgot register?")
@ -237,6 +280,17 @@ func RegisterSysEmsLog(i ISysEmsLog) {
localSysEmsLog = i
}
func SysAddons() ISysAddons {
if localSysAddons == nil {
panic("implement not found for interface ISysAddons, forgot register?")
}
return localSysAddons
}
func RegisterSysAddons(i ISysAddons) {
localSysAddons = i
}
func SysConfig() ISysConfig {
if localSysConfig == nil {
panic("implement not found for interface ISysConfig, forgot register?")
@ -248,15 +302,37 @@ func RegisterSysConfig(i ISysConfig) {
localSysConfig = i
}
func SysCron() ISysCron {
if localSysCron == nil {
panic("implement not found for interface ISysCron, forgot register?")
func SysLog() ISysLog {
if localSysLog == nil {
panic("implement not found for interface ISysLog, forgot register?")
}
return localSysCron
return localSysLog
}
func RegisterSysCron(i ISysCron) {
localSysCron = i
func RegisterSysLog(i ISysLog) {
localSysLog = i
}
func SysLoginLog() ISysLoginLog {
if localSysLoginLog == nil {
panic("implement not found for interface ISysLoginLog, forgot register?")
}
return localSysLoginLog
}
func RegisterSysLoginLog(i ISysLoginLog) {
localSysLoginLog = i
}
func SysProvinces() ISysProvinces {
if localSysProvinces == nil {
panic("implement not found for interface ISysProvinces, forgot register?")
}
return localSysProvinces
}
func RegisterSysProvinces(i ISysProvinces) {
localSysProvinces = i
}
func SysCronGroup() ISysCronGroup {
@ -281,48 +357,15 @@ func RegisterSysCurdDemo(i ISysCurdDemo) {
localSysCurdDemo = i
}
func SysGenCodes() ISysGenCodes {
if localSysGenCodes == nil {
panic("implement not found for interface ISysGenCodes, forgot register?")
func SysDictData() ISysDictData {
if localSysDictData == nil {
panic("implement not found for interface ISysDictData, forgot register?")
}
return localSysGenCodes
return localSysDictData
}
func RegisterSysGenCodes(i ISysGenCodes) {
localSysGenCodes = i
}
func SysLog() ISysLog {
if localSysLog == nil {
panic("implement not found for interface ISysLog, forgot register?")
}
return localSysLog
}
func RegisterSysLog(i ISysLog) {
localSysLog = i
}
func SysAddons() ISysAddons {
if localSysAddons == nil {
panic("implement not found for interface ISysAddons, forgot register?")
}
return localSysAddons
}
func RegisterSysAddons(i ISysAddons) {
localSysAddons = i
}
func SysAddonsConfig() ISysAddonsConfig {
if localSysAddonsConfig == nil {
panic("implement not found for interface ISysAddonsConfig, forgot register?")
}
return localSysAddonsConfig
}
func RegisterSysAddonsConfig(i ISysAddonsConfig) {
localSysAddonsConfig = i
func RegisterSysDictData(i ISysDictData) {
localSysDictData = i
}
func SysDictType() ISysDictType {
@ -336,57 +379,13 @@ func RegisterSysDictType(i ISysDictType) {
localSysDictType = i
}
func SysLoginLog() ISysLoginLog {
if localSysLoginLog == nil {
panic("implement not found for interface ISysLoginLog, forgot register?")
func SysCron() ISysCron {
if localSysCron == nil {
panic("implement not found for interface ISysCron, forgot register?")
}
return localSysLoginLog
return localSysCron
}
func RegisterSysLoginLog(i ISysLoginLog) {
localSysLoginLog = i
}
func SysAttachment() ISysAttachment {
if localSysAttachment == nil {
panic("implement not found for interface ISysAttachment, forgot register?")
}
return localSysAttachment
}
func RegisterSysAttachment(i ISysAttachment) {
localSysAttachment = i
}
func SysProvinces() ISysProvinces {
if localSysProvinces == nil {
panic("implement not found for interface ISysProvinces, forgot register?")
}
return localSysProvinces
}
func RegisterSysProvinces(i ISysProvinces) {
localSysProvinces = i
}
func SysServeLog() ISysServeLog {
if localSysServeLog == nil {
panic("implement not found for interface ISysServeLog, forgot register?")
}
return localSysServeLog
}
func RegisterSysServeLog(i ISysServeLog) {
localSysServeLog = i
}
func SysSmsLog() ISysSmsLog {
if localSysSmsLog == nil {
panic("implement not found for interface ISysSmsLog, forgot register?")
}
return localSysSmsLog
}
func RegisterSysSmsLog(i ISysSmsLog) {
localSysSmsLog = i
func RegisterSysCron(i ISysCron) {
localSysCron = i
}

View File

@ -31,9 +31,9 @@ var (
// Start 启动
func Start(c context.Context) {
ctxManager = c
g.Log().Debug(ctxManager, "start websocket..")
go clientManager.start()
go clientManager.ping()
g.Log().Debug(ctxManager, "start websocket..")
}
// Stop 关闭

View File

@ -19,9 +19,9 @@ func handlerMsg(client *Client, message []byte) {
g.Log().Warningf(ctxManager, "handlerMsg recover, err:%+v, stack:%+v", r, string(debug.Stack()))
}
}()
request := &WRequest{}
err := gconv.Struct(message, request)
if err != nil {
var request *WRequest
if err := gconv.Struct(message, &request); err != nil {
g.Log().Warningf(ctxManager, "handlerMsg 数据解析失败,err:%+v, message:%+v", err, string(message))
return
}
@ -31,8 +31,6 @@ func handlerMsg(client *Client, message []byte) {
return
}
//g.Log().Infof(ctxManager, "websocket handlerMsg:%+v", request)
fun, ok := routers[request.Event]
if !ok {
g.Log().Warningf(ctxManager, "handlerMsg function id %v: not registered", request.Event)

View File

@ -15,6 +15,7 @@ import (
"hotgo/internal/cmd"
"hotgo/internal/global"
_ "hotgo/internal/logic"
_ "hotgo/internal/queues"
)
func main() {

View File

@ -246,3 +246,5 @@ hggen:
addon:
srcPath: "./resource/generate/default/addon" # 生成模板路径
templatePath: "./resource/template/addons/{$name}" # 页面模板路径
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@ var (
defaultRowStyle = `{"font":{"color":"#666666","size":13,"family":"arial"},"alignment":{"vertical":"center","horizontal":"center"}}`
)
// ExportByStructs 导出切片结构体
// ExportByStructs 导出切片结构体到excel表格
func ExportByStructs(ctx context.Context, tags []string, list interface{}, fileName string, sheetName string) (err error) {
f := excelize.NewFile()
f.SetSheetName("Sheet1", sheetName)

View File

@ -10,7 +10,6 @@ import (
"github.com/gogf/gf/v2/os/gfile"
"hotgo/utility/format"
"io/ioutil"
"os"
"path/filepath"
)
@ -25,53 +24,6 @@ type fileInfo struct { //文件信息
size int64
}
func PathExists(path string) (bool, error) {
info, err := os.Stat(path)
if err == nil {
return info.IsDir(), nil
}
return false, err
}
func FileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// HasDir 判断文件夹是否存在
func HasDir(path string) (bool, error) {
_, _err := os.Stat(path)
if _err == nil {
return true, nil
}
if os.IsNotExist(_err) {
return false, nil
}
return false, _err
}
// CreateDir 创建文件夹
func CreateDir(path string) (err error) {
_exist, err := HasDir(path)
if err != nil {
return
}
if !_exist {
err = os.Mkdir(path, os.ModePerm)
if err != nil {
return
}
}
return
}
// WalkDir 递归获取目录下文件的名称和大小
func WalkDir(dirname string) (error, []fileInfo) {
op, err := filepath.Abs(dirname) //获取目录的绝对路径

View File

@ -38,6 +38,7 @@ func CheckPassword(input, salt, hash string) (err error) {
return
}
// SafeGo 安全的调用协程遇到错误时输出错误日志而不是抛出panic
func SafeGo(ctx context.Context, f func(ctx context.Context), level ...interface{}) {
go func() {
defer func() {

View File

@ -10,6 +10,10 @@ import (
"context"
)
// Filter 通用过滤器
// Filter 预处理和数据过滤接口目前主要用于input层的输入过滤和内部效验。
// 你可以在任意地方使用它只要实现了Filter接口即可
type Filter interface {
// Filter gf效验规则 https://goframe.org/pages/viewpage.action?pageId=1114367
Filter(ctx context.Context) error

View File

@ -9,22 +9,9 @@ package validate
import (
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"time"
)
// InSameDay 是否为同一天
func InSameDay(t1, t2 int64) bool {
y1, m1, d1 := time.Unix(t1, 0).Date()
y2, m2, d2 := time.Unix(t2, 0).Date()
return y1 == y2 && m1 == m2 && d1 == d2
}
// InSameMinute 是否为同一分钟
func InSameMinute(t1, t2 int64) bool {
d1 := time.Unix(t1, 0).Format("2006-01-02 15:04")
d2 := time.Unix(t2, 0).Format("2006-01-02 15:04")
return d1 == d2
}
// 是否包含判断
// InSliceExistStr 判断字符或切片字符是否存在指定字符
func InSliceExistStr(elems interface{}, search string) bool {

View File

@ -14,14 +14,19 @@ import (
"net"
"net/url"
"regexp"
"time"
)
// 是否判断
// IsDNSName 是否是域名地址
func IsDNSName(s string) bool {
DNSName := `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
rxDNSName := regexp.MustCompile(DNSName)
return s != "" && rxDNSName.MatchString(s)
}
// IsHTTPS 是否是https请求
func IsHTTPS(ctx context.Context) bool {
r := ghttp.RequestFromCtx(ctx)
if r == nil {
@ -130,3 +135,17 @@ func IsIDCard(idCard string) bool {
m := sum % 11
return validate[m] == idCard[sz-1]
}
// IsSameDay 是否为同一天
func IsSameDay(t1, t2 int64) bool {
y1, m1, d1 := time.Unix(t1, 0).Date()
y2, m2, d2 := time.Unix(t2, 0).Date()
return y1 == y2 && m1 == m2 && d1 == d2
}
// IsSameMinute 是否为同一分钟
func IsSameMinute(t1, t2 int64) bool {
d1 := time.Unix(t1, 0).Format("2006-01-02 15:04")
d2 := time.Unix(t2, 0).Format("2006-01-02 15:04")
return d1 == d2
}

View File

@ -1,6 +1,6 @@
{
"name": "hotgo",
"version": "2.2.10",
"version": "2.3.5",
"author": {
"name": "MengShuai",
"email": "133814250@qq.com",

View File

View File

@ -44,7 +44,7 @@ export function DeleteDict(params?) {
*/
export function getDictSelect(params?) {
return http.request({
url: '/dictType/select',
url: '/dictType/tree',
method: 'GET',
params,
});

View File

@ -1,8 +1,8 @@
export enum SocketEnum {
EventPing = 'ping',
EventConnected = 'connected',
EventAdminMonitorTrends = 'adminMonitorTrends',
EventAdminMonitorRunInfo = 'adminMonitorRunInfo',
EventAdminMonitorTrends = 'admin/monitor/trends',
EventAdminMonitorRunInfo = 'admin/monitor/runInfo',
TypeQueryUser = 2,
TypeBoardCastMsg = 3,
TypeQuerySwitcher = 4,

View File

@ -22,7 +22,10 @@
</n-descriptions-item>
<n-descriptions-item label="文档地址">
<div class="flex items-center">
<a href="https://github.com/bufanyun/hotgo/tree/v2.0/docs" class="py-2" target="_blank"
<a
href="https://github.com/bufanyun/hotgo/tree/v2.0/docs/guide-zh-CN"
class="py-2"
target="_blank"
>查看文档地址</a
>
</div>

View File

View File

@ -160,7 +160,7 @@
width: 220,
title: '操作',
key: 'action',
fixed: 'right',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',

View File

@ -114,7 +114,7 @@
width: 220,
title: '操作',
key: 'action',
fixed: 'right',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',

View File

@ -49,7 +49,7 @@
}"
>
<n-alert :show-icon="false" type="info">
注意插件创建成功后会在服务端对应的项目目录中创建插件模块并自动注册到当前项目中
注意插件创建成功后会在服务端对应的项目目录中生成插件模块文件并自动注册到当前项目中
</n-alert>
<n-form
:model="formParams"
@ -427,10 +427,21 @@
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Build(formParams.value).then((_res) => {
showModal.value = false;
buildSuccessNotify();
dialog.info({
title: '提示',
content:
'你确定要提交生成吗?热编译生成后如果列表自动刷新没有出现新插件请等几秒刷新即可。否则请手动重启服务后刷新页面!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Build(formParams.value).then((_res) => {
showModal.value = false;
buildSuccessNotify();
});
},
onNegativeClick: () => {
// message.error('');
},
});
} else {
message.error('请填写完整信息');

View File

@ -183,7 +183,7 @@
width: 220,
title: '操作',
key: 'action',
fixed: 'right',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',

View File

@ -186,7 +186,7 @@
width: 220,
title: '操作',
key: 'action',
fixed: 'right',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',

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