mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-13 20:53:49 +08:00
发布v2.13.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
- [消息队列](sys-queue.md)
|
||||
- [功能扩展库](sys-library.md)
|
||||
- [工具方法](sys-utility.md)
|
||||
- Websocket服务器
|
||||
- [WebSocket服务器](sys-websocket-server.md)
|
||||
- [TCP服务器](sys-tcp-server.md)
|
||||
- [单元测试](sys-test.md)
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
|
||||
### 前端开发
|
||||
- [表单组件](web-form.md)
|
||||
- Websocket客户端
|
||||
- 工具库
|
||||
- [WebSocket客户端](sys-websocket-client.md)
|
||||
- [独立部署](web-deploy.md)
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
|
||||
1. /server/addons/hgexample/ # 插件模块目录
|
||||
2. /server/addons/modules/hgexample.go # 隐式注册插件文件
|
||||
3. /web/src/api/addons/hgexample # webApi目录
|
||||
4. /web/src/views/addons/hgexample # web页面目录
|
||||
3. /server/resource/addons/hgexample # 静态资源和页面模板目录,属于扩展功能选项,勾选对应选项后才会生成
|
||||
4. /web/src/api/addons/hgexample # webApi目录
|
||||
5. /web/src/views/addons/hgexample # web页面目录
|
||||
|
||||
# 默认情况下没有为web页面生成菜单权限,因为在实际场景中插件不一定需要用到web页面,所以如有需要请手动到后台 权限管理 -> 菜单权限->自行添加菜单和配置权限
|
||||
```
|
||||
|
||||
@@ -30,13 +30,13 @@ func (s *Skeleton) GetModule() Module {
|
||||
|
||||
// Module 插件模块
|
||||
type Module interface {
|
||||
Init(ctx context.Context) // 初始化
|
||||
InitRouter(ctx context.Context, group *ghttp.RouterGroup) // 初始化并注册路由
|
||||
Ctx() context.Context // 上下文
|
||||
GetSkeleton() *Skeleton // 架子
|
||||
Install(ctx context.Context) error // 安装模块
|
||||
Upgrade(ctx context.Context) error // 更新模块
|
||||
UnInstall(ctx context.Context) error // 卸载模块
|
||||
Start(option *Option) (err error) // 启动模块
|
||||
Stop() (err error) // 停止模块
|
||||
Ctx() context.Context // 上下文
|
||||
GetSkeleton() *Skeleton // 获取模块
|
||||
Install(ctx context.Context) (err error) // 安装模块
|
||||
Upgrade(ctx context.Context) (err error) // 更新模块
|
||||
UnInstall(ctx context.Context) (err error) // 卸载模块
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
BIN
docs/guide-zh-CN/images/sys-library-dict.png
Normal file
BIN
docs/guide-zh-CN/images/sys-library-dict.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
@@ -26,6 +26,6 @@
|
||||
> 需要本地具有 git node golang 环境
|
||||
|
||||
- node版本 >= 16.0.0
|
||||
- golang版本 >= v1.19
|
||||
- mysql 引擎需要是 innoDB
|
||||
- golang版本 >= 1.19
|
||||
- mysql版本 >= 5.7,引擎需要是 innoDB
|
||||
- IDE推荐:Goland
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
- node版本 >= v16.0.0
|
||||
- golang版本 >= v1.19
|
||||
- goframe版本 >=v2.6.1
|
||||
- goframe版本 >=v2.6.4
|
||||
- mysql版本 >=5.7
|
||||
|
||||
> 必须先看[环境搭建文档](start-environment.md),如果安装遇到问题务必先查看[常见问题文档](start-issue.md)
|
||||
|
||||
@@ -11,6 +11,20 @@
|
||||
|
||||
> 如果升级(覆盖)代码后打开会出现 sql 报错, 请检查更新的数据库格式或自行调整
|
||||
|
||||
### v2.13.1
|
||||
updated 2024.3.7
|
||||
|
||||
- 增加:增加内置数据字典类型:`枚举字典`和`自定义方法字典`,支持代码生成时关联选项使用
|
||||
- 增加:增加大文件上传,支持分片上传、断点续传,存储驱动已适配`本地存储`
|
||||
- 增加:插件模块增加停止服务回调接口,调整静态资源默认存放位置,创建插件选项增加可选扩展功能
|
||||
- 增加:功能案例插件增加`30+`常用组件示例,增加`websocket`消息收发测试
|
||||
- 增加:文档增`加功能扩展库`、`websocket服务器`、`websocket客户端`使用说明,当前版本文档已完善
|
||||
- 修复:修复省市区无法添加地区问题
|
||||
- 优化:gf版本升级到v2.6.4
|
||||
- 优化:优化缓存组件依赖关系
|
||||
- 优化:调整部分前端表格自适应宽度
|
||||
- 优化:HTTP错误码接管统一改为由响应中间件处理
|
||||
|
||||
### v2.12.1
|
||||
updated 2023.12.29
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ graph TD
|
||||
#### 如何区分部门和下级用户?
|
||||
|
||||
- 在实际使用时,部门更多的是在公司或机构中使用,可以通过在 组织管理 -> 后台用户 ->为用户绑定部门
|
||||
- 下级用户在代理商或分销系统中比较常见,后台用户由谁添加的,那么被添加的用户就是其下级用户。后续也将开放邀请码绑定下级功能。
|
||||
- 下级用户在代理商或分销系统中比较常见,后台用户由谁添加的,那么被添加的用户就是其下级用户
|
||||
|
||||
#### 如何判断数据是谁的?
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- 缓存驱动
|
||||
- 请求上下文
|
||||
- JWT
|
||||
- 数据字典
|
||||
- 地理定位(待写)
|
||||
- 通知(待写)
|
||||
|
||||
@@ -151,6 +152,159 @@ func test(ctx context.Context) {
|
||||
|
||||
```
|
||||
|
||||
### 数据字典
|
||||
|
||||
- hotgo增加了对枚举字典和自定义方法字典的内置支持,从而在系统中经常使用的一些特定数据维护基础上做出了增强。
|
||||
|
||||
#### 字典数据选项
|
||||
- 文件路径:server/internal/model/dict.go
|
||||
```go
|
||||
package model
|
||||
|
||||
// Option 字典数据选项
|
||||
type Option struct {
|
||||
Key interface{} `json:"key"`
|
||||
Label string `json:"label" description:"字典标签"`
|
||||
Value interface{} `json:"value" description:"字典键值"`
|
||||
ValueType string `json:"valueType" description:"键值数据类型"`
|
||||
Type string `json:"type" description:"字典类型"`
|
||||
ListClass string `json:"listClass" description:"表格回显样式"`
|
||||
}
|
||||
```
|
||||
|
||||
#### 枚举字典
|
||||
- 适用于系统开发期间内置的枚举数据,这样即维护了枚举值,又关联了数据字典
|
||||
|
||||
##### 一个例子
|
||||
- 定义枚举值和字典数据选项,并注册字典类型
|
||||
- 文件路径:server/internal/consts/credit_log.go
|
||||
|
||||
```go
|
||||
package consts
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dict.RegisterEnums("creditType", "资金变动类型", CreditTypeOptions)
|
||||
dict.RegisterEnums("creditGroup", "资金变动分组", CreditGroupOptions)
|
||||
}
|
||||
|
||||
const (
|
||||
CreditTypeBalance = "balance" // 余额
|
||||
CreditTypeIntegral = "integral" // 积分
|
||||
)
|
||||
|
||||
const (
|
||||
CreditGroupDecr = "decr" // 扣款
|
||||
CreditGroupIncr = "incr" // 加款
|
||||
CreditGroupOpDecr = "op_decr" // 操作扣款
|
||||
CreditGroupOpIncr = "op_incr" // 操作加款
|
||||
CreditGroupBalanceRecharge = "balance_recharge" // 余额充值
|
||||
CreditGroupBalanceRefund = "balance_refund" // 余额退款
|
||||
CreditGroupApplyCash = "apply_cash" // 申请提现
|
||||
)
|
||||
|
||||
// CreditTypeOptions 变动类型
|
||||
var CreditTypeOptions = []*model.Option{
|
||||
dict.GenSuccessOption(CreditTypeBalance, "余额"),
|
||||
dict.GenInfoOption(CreditTypeIntegral, "积分"),
|
||||
}
|
||||
|
||||
// CreditGroupOptions 变动分组
|
||||
var CreditGroupOptions = []*model.Option{
|
||||
dict.GenWarningOption(CreditGroupDecr, "扣款"),
|
||||
dict.GenSuccessOption(CreditGroupIncr, "加款"),
|
||||
dict.GenWarningOption(CreditGroupOpDecr, "操作扣款"),
|
||||
dict.GenSuccessOption(CreditGroupOpIncr, "操作加款"),
|
||||
dict.GenWarningOption(CreditGroupBalanceRefund, "余额退款"),
|
||||
dict.GenSuccessOption(CreditGroupBalanceRecharge, "余额充值"),
|
||||
dict.GenInfoOption(CreditGroupApplyCash, "申请提现"),
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### 自定义方法字典
|
||||
- 适用于非固定选项,如数据是从某个表/文件读取或从第三方读取,数据需要进行转换时使用
|
||||
|
||||
##### 方法字典接口
|
||||
- 文件路径:server/internal/consts/credit_log.go
|
||||
```go
|
||||
package dict
|
||||
|
||||
// FuncDict 方法字典,实现本接口即可使用内置方法字典
|
||||
type FuncDict func(ctx context.Context) (res []*model.Option, err error)
|
||||
```
|
||||
|
||||
##### 一个例子
|
||||
- 定义获取字典数据方法,并注册字典类型
|
||||
- 文件路径:server/internal/logic/admin/post.go
|
||||
|
||||
```go
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
type sAdminPost struct{}
|
||||
|
||||
func NewAdminPost() *sAdminPost {
|
||||
return &sAdminPost{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAdminPost(NewAdminPost())
|
||||
dict.RegisterFunc("adminPostOption", "岗位选项", service.AdminPost().Option)
|
||||
}
|
||||
|
||||
// Option 岗位选项
|
||||
func (s *sAdminPost) Option(ctx context.Context) (opts []*model.Option, err error) {
|
||||
var list []*entity.AdminPost
|
||||
if err = dao.AdminPost.Ctx(ctx).OrderAsc(dao.AdminPost.Columns().Sort).Scan(&list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
opts = make([]*model.Option, 0)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range list {
|
||||
opts = append(opts, dict.GenHashOption(v.Id, v.Name))
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
#### 代码生成支持
|
||||
- 内置的枚举字典和自定义方法字典在生成代码时可以直接进行选择,生成代码格式和系统字典管理写法一致
|
||||
|
||||

|
||||
|
||||
|
||||
#### 内置字典和系统字典的区分
|
||||
|
||||
##### 主要区别
|
||||
- 系统字典由表:`hg_sys_dict_type`和`hg_sys_dict_data`共同进行维护,使用时需通过后台到字典管理中进行添加
|
||||
- 内置字典是系统开发期间在代码层面事先定义和注册好的数据选项
|
||||
|
||||
|
||||
##### 数据格式区别
|
||||
- 系统字典所有ID都是大于0的int64类型
|
||||
- 内置字典ID都是小于0的int64类型。枚举字典以20000开头,如:-200001381053496;方法字典以30000开头,如:-30000892528327;开头以外数字是根据数据选项的`key`值进行哈希算法得出
|
||||
|
||||
### 地理定位
|
||||
```go
|
||||
// 待写
|
||||
|
||||
@@ -55,7 +55,7 @@ func main() {
|
||||
|
||||
|
||||
### 注册支付回调
|
||||
- 在文件`server/internal/global/pay.go` 加入你的业务订单分组回调方法,当订单支付成功验签通过后会自动进行回调,参考以下:
|
||||
- 在文件`server/internal/logic/pay/notify.go` 加入你的业务订单分组回调方法,当订单支付成功验签通过后会自动进行回调,参考以下:
|
||||
|
||||
```go
|
||||
package global
|
||||
@@ -66,12 +66,13 @@ import (
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
// 注册支付成功回调方法
|
||||
func payNotifyCall() {
|
||||
payment.RegisterNotifyCall(consts.OrderGroupAdminOrder, service.AdminOrder().PayNotify) // 后台充值订单
|
||||
// ...
|
||||
// RegisterNotifyCall 注册支付成功回调方法
|
||||
func (s *sPay) RegisterNotifyCall() {
|
||||
payment.RegisterNotifyCallMap(map[string]payment.NotifyCallFunc{
|
||||
consts.OrderGroupAdminOrder: service.AdminOrder().PayNotify, // 后台充值订单
|
||||
// ...
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 订单退款
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- 服务认证
|
||||
- 更多
|
||||
|
||||
> HotGo基于GF框架的TCP服务器组件,提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能,如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等,大大简化和规范了服务器开发流程。
|
||||
> HotGo基于GoFrame的TCP服务器组件,提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能,如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等,大大简化和规范了服务器开发流程。
|
||||
|
||||
### 配置文件
|
||||
- 配置文件:server/manifest/config/config.yaml
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
## WebHook
|
||||
|
||||
待写
|
||||
请参考:https://goframe.org/pages/viewpage.action?pageId=1114387
|
||||
|
||||
210
docs/guide-zh-CN/sys-websocket-client.md
Normal file
210
docs/guide-zh-CN/sys-websocket-client.md
Normal file
@@ -0,0 +1,210 @@
|
||||
## WebSocket客户端
|
||||
|
||||
目录
|
||||
|
||||
- 全局消息监听
|
||||
- 单页面消息监听
|
||||
- 发送消息
|
||||
|
||||
> 基于WebSocket服务器,hotgo还对客户端的上做了一些封装,使其使用起来更加方便
|
||||
- [WebSocket服务器](sys-websocket-server.md)
|
||||
|
||||
### 全局消息监听
|
||||
- 所有全局的消息监听都在这里
|
||||
- 文件路径:web/src/utils/websocket/registerMessage.ts
|
||||
```ts
|
||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||
import { SocketEnum } from '@/enums/socketEnum';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import { notificationStoreWidthOut } from '@/store/modules/notification';
|
||||
import { addOnMessage, WebSocketMessage } from '@/utils/websocket/index';
|
||||
|
||||
// 注册全局消息监听
|
||||
export function registerGlobalMessage() {
|
||||
// 心跳
|
||||
addOnMessage(SocketEnum.EventPing, function (_message: WebSocketMessage) {
|
||||
// console.log('ping..');
|
||||
});
|
||||
|
||||
// 强制退出
|
||||
addOnMessage(SocketEnum.EventKick, function (_message: WebSocketMessage) {
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
useUserStore.logout().then(() => {
|
||||
// 移除标签页
|
||||
localStorage.removeItem(TABS_ROUTES);
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
// 消息通知
|
||||
addOnMessage(SocketEnum.EventNotice, function (message: WebSocketMessage) {
|
||||
const notificationStore = notificationStoreWidthOut();
|
||||
notificationStore.triggerNewMessages(message.data);
|
||||
});
|
||||
|
||||
// 更多全局消息处理都可以在这里注册
|
||||
// ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 单页面消息监听
|
||||
- 当你只需要某个页面使用WebSocket,这将是一个不错的选择,下面是一个简单的演示例子
|
||||
- 文件路径:web/src/views/addons/hgexample/portal/websocketTest.vue
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="测试websocket">
|
||||
尝试在下方输入框中输入任意文字消息内容,发送后websocket服务器收到会原样返回
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-space vertical>
|
||||
<n-input-group style="width: 520px">
|
||||
<n-input
|
||||
@keyup.enter="sendMessage"
|
||||
:style="{ width: '78%' }"
|
||||
placeholder="请输入消息内容"
|
||||
:on-focus="onFocus"
|
||||
:on-blur="onBlur"
|
||||
v-model:value="inputMessage"
|
||||
/>
|
||||
<n-button type="primary" @click="sendMessage"> 发送消息</n-button>
|
||||
</n-input-group>
|
||||
|
||||
<div class="mt-5"></div>
|
||||
|
||||
<n-timeline :icon-size="20">
|
||||
<n-timeline-item color="grey" content="输入中.." v-if="isInput">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MessageOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
|
||||
<n-timeline-item
|
||||
v-for="item in messages"
|
||||
:key="item"
|
||||
:type="item.type == Enum.SendType ? 'success' : 'info'"
|
||||
:title="item.type == Enum.SendType ? '发送消息' : '收到消息'"
|
||||
:content="item.content"
|
||||
:time="item.time"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SendOutlined v-if="item.type == Enum.SendType" />
|
||||
<SoundOutlined v-if="item.type == Enum.ReceiveType" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
</n-timeline>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { MessageOutlined, SendOutlined, SoundOutlined } from '@vicons/antd';
|
||||
import { format } from 'date-fns';
|
||||
import { addOnMessage, removeOnMessage, sendMsg, WebSocketMessage } from '@/utils/websocket';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
interface Message {
|
||||
type: Enum;
|
||||
content: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
const message = useMessage();
|
||||
const messages = ref<Message[]>([]);
|
||||
const inputMessage = ref('你好,HotGo');
|
||||
const isInput = ref(false);
|
||||
const testMessageEvent = 'admin/addons/hgexample/testMessage';
|
||||
|
||||
enum Enum {
|
||||
SendType = 1, // 发送类型
|
||||
ReceiveType = 2, // 接受类型
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
isInput.value = true;
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
isInput.value = false;
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
if (inputMessage.value == '') {
|
||||
message.error('消息内容不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
sendMsg(testMessageEvent, {
|
||||
message: inputMessage.value,
|
||||
});
|
||||
|
||||
const msg: Message = {
|
||||
type: Enum.SendType,
|
||||
content: inputMessage.value,
|
||||
time: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||
};
|
||||
insertMessage(msg);
|
||||
inputMessage.value = '';
|
||||
}
|
||||
|
||||
// 存入本地记录
|
||||
function insertMessage(msg: Message): void {
|
||||
messages.value.unshift(msg); // 在头部插入消息
|
||||
if (messages.value.length > 10) {
|
||||
messages.value = messages.value.slice(0, 10); // 如果超过10个,则只保留最前面10个
|
||||
}
|
||||
}
|
||||
|
||||
const onMessage = (res: WebSocketMessage) => {
|
||||
const msg: Message = {
|
||||
type: Enum.ReceiveType,
|
||||
content: res.data.message,
|
||||
time: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||
};
|
||||
insertMessage(msg);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 在当前页面注册消息监听
|
||||
addOnMessage(testMessageEvent, onMessage);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 移除消息监听
|
||||
removeOnMessage(testMessageEvent);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
```
|
||||
|
||||
#### 发送消息
|
||||
- 向服务器发送一条消息
|
||||
```ts
|
||||
import { sendMsg } from '@/utils/websocket';
|
||||
|
||||
const event = 'admin/addons/hgexample/testMessage'; // 消息路由
|
||||
const data: object | null = { // 消息内容
|
||||
message: 'message content...',
|
||||
};
|
||||
const isRetry = false; // 发送失败是否重试,不传默认为true
|
||||
|
||||
// 基本使用
|
||||
sendMsg(event, data);
|
||||
|
||||
// 无消息内容
|
||||
sendMsg(event);
|
||||
|
||||
// 发送失败不重试
|
||||
sendMsg(event, data, isRetry);
|
||||
```
|
||||
143
docs/guide-zh-CN/sys-websocket-server.md
Normal file
143
docs/guide-zh-CN/sys-websocket-server.md
Normal file
@@ -0,0 +1,143 @@
|
||||
## WebSocket服务器
|
||||
|
||||
目录
|
||||
|
||||
- 一个基本的消息收发例子
|
||||
- 常用方法
|
||||
- HTTP接口
|
||||
- 其他
|
||||
|
||||
> hotgo提供了一个WebSocket服务器,随`HTTP服务`启停。集成了许多常用功能,如JWT身份认证、路由消息处理器、一对一消息/群组消息/广播消息、在线用户管理、心跳保持等,大大简化和规范了WebSocket服务器的开发流程。
|
||||
- [Websocket客户端](sys-websocket-client.md)
|
||||
|
||||
### 一个基本的消息收发例子
|
||||
- 这是一个基本的消息接收并进行处理的简单例子
|
||||
|
||||
#### 1.消息处理接口
|
||||
- 消息处理在设计上采用了接口化的思路。只需要实现以下接口,即可进行WebSocket消息注册
|
||||
- 文件路径:server/internal/websocket/model.go
|
||||
```go
|
||||
package websocket
|
||||
|
||||
// EventHandler 消息处理器
|
||||
type EventHandler func(client *Client, req *WRequest)
|
||||
```
|
||||
|
||||
#### 2.定义消息处理方法
|
||||
- 以下是功能案例中的一个简单演示,实现了消息处理接口,并将收到的消息原样发送给客户端
|
||||
- 文件路径:server/addons/hgexample/controller/websocket/handler/index.go
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
Index = cIndex{}
|
||||
)
|
||||
|
||||
type cIndex struct{}
|
||||
|
||||
// TestMessage 测试消息
|
||||
func (c *cIndex) TestMessage(client *websocket.Client, req *websocket.WRequest) {
|
||||
g.Log().Infof(client.Context(), "收到客户端测试消息:%v", gjson.New(req).String())
|
||||
// 将收到的消息原样发送给客户端
|
||||
websocket.SendSuccess(client, req.Event, req.Data)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.注册消息
|
||||
- 定义消息处理方法后,需要将其注册到WebSocket消息处理器,一般放在对应应用模块的`router/websocket.go`下即可
|
||||
- 文件路径:server/addons/hgexample/router/websocket.go
|
||||
```go
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"hotgo/addons/hgexample/controller/websocket"
|
||||
"hotgo/addons/hgexample/controller/websocket/handler"
|
||||
ws "hotgo/internal/websocket"
|
||||
)
|
||||
|
||||
// WebSocket ws路由配置
|
||||
func WebSocket(ctx context.Context, group *ghttp.RouterGroup) {
|
||||
// 注册消息路由
|
||||
ws.RegisterMsg(ws.EventHandlers{
|
||||
"admin/addons/hgexample/testMessage": handler.Index.TestMessage, // 测试消息
|
||||
})
|
||||
|
||||
// 这里"admin/addons/hgexample/testMessage"代表的是一个消息处理ID,可以自定义。建议的格式是和HTTP接口格式保持一致,这样还可以便于对用户请求的消息进行权限验证
|
||||
// 客户端连接后,向WebSocket服务器发送event为"admin/addons/hgexample/testMessage"的消息时,会调用TestMessage方法
|
||||
}
|
||||
```
|
||||
|
||||
- 到此,你已了解了WebSocket消息接收并进行处理的基本流程
|
||||
|
||||
|
||||
### 常用方法
|
||||
- websocket服务器还提供了一些常用的方法,下面只对部分进行说明
|
||||
```go
|
||||
func test() {
|
||||
websocket.SendToAll() // 发送全部客户端
|
||||
websocket.SendToClientID() // 发送单个客户端
|
||||
websocket.SendToUser() // 发送单个用户
|
||||
websocket.SendToTag() // 发送某个标签、群组
|
||||
|
||||
client := websocket.Manager().GetClient(id) // 通过连接ID获取客户端连接
|
||||
client := websocket.Manager().GetUserClient(userId) // 通过用户ID获取客户端连接,因为用户是可多端登录的,这里返回的是一个切片
|
||||
|
||||
websocket.SendSuccess(client, "admin/addons/hgexample/testMessage", "消息内容") // 向指定客户端发送一条成功的消息
|
||||
websocket.SendError(client, "admin/addons/hgexample/testMessage", gerror.New("错误内容")) // 向指定客户端发送一条失败的消息
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### HTTP接口
|
||||
- 你还可以通过http接口方式调用WebSocket发送消息
|
||||
- 参考文件:server/internal/controller/websocket/send.go
|
||||
|
||||
|
||||
### 其他
|
||||
- WebSocket被连接时需验证用户认证中间件,所以用户必须登录成功后才能连接成功
|
||||
- 参考文件:server/internal/logic/middleware/weboscket_auth.go
|
||||
```go
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/response"
|
||||
"hotgo/utility/simple"
|
||||
)
|
||||
|
||||
// WebSocketAuth websocket鉴权中间件
|
||||
func (s *sMiddleware) WebSocketAuth(r *ghttp.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
path = gstr.Replace(r.URL.Path, simple.RouterPrefix(ctx, consts.AppWebSocket), "", 1)
|
||||
)
|
||||
|
||||
// 不需要验证登录的路由地址
|
||||
if s.IsExceptLogin(ctx, consts.AppWebSocket, path) {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息传递到上下文中
|
||||
if err := s.DeliverUserContext(r); err != nil {
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
r.Middleware.Next()
|
||||
}
|
||||
```
|
||||
|
||||
- 如果您不要求用户进行登录即可使用 WebSocket,那么需要对身份验证中间件进行修改。然而,这样做会降低连接的安全性,并且无法应用于需要确定用户身份的情景,因此并不建议采取这种策略
|
||||
@@ -21,6 +21,7 @@
|
||||
- 单文件上传 UploadFile
|
||||
- 多文件上传 UploadFile
|
||||
- 文件选择器 FileChooser
|
||||
- 大文件上传 MultipartUpload
|
||||
- 开关 Switch
|
||||
- 评分 Rate
|
||||
- 省市区选择器 CitySelector
|
||||
@@ -795,6 +796,33 @@ type FileType = 'image' | 'doc' | 'audio' | 'video' | 'zip' | 'other' | 'default
|
||||
<FileChooser v-model:value="value" :maxNumber="10" fileType="image" />
|
||||
```
|
||||
|
||||
### 大文件上传 MultipartUpload
|
||||
- 基础用法
|
||||
```vue
|
||||
<template>
|
||||
<MultipartUpload ref="multipartUploadRef" @onFinish="handleFinishCall" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import MultipartUpload from '@/components/Upload/multipartUpload.vue';
|
||||
import { Attachment } from '@/components/FileChooser/src/model';
|
||||
const multipartUploadRef = ref();
|
||||
|
||||
// 打开上传Modal
|
||||
function handleMultipartUpload() {
|
||||
multipartUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
// 上传成功回调附件内容
|
||||
function handleFinishCall(result: Attachment, success: boolean) {
|
||||
if (success) {
|
||||
reloadTable();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 开关 Switch
|
||||
```vue
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user