mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-08 18:23:48 +08:00
发布v2.3.5版本,本次为优化版本。更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
err = gfile.PutContents(modulesPath, gstr.ReplaceByMap(importModules, replaces))
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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>`
|
||||
)
|
||||
|
||||
225
server/internal/library/addons/build_layout.go
Normal file
225
server/internal/library/addons/build_layout.go
Normal 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>
|
||||
`
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
73
server/internal/library/queue/consumer.go
Normal file
73
server/internal/library/queue/consumer.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Disk 磁盘队列
|
||||
|
||||
type DiskProducerMq struct {
|
||||
config *disk.Config
|
||||
producers map[string]*disk.Queue
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user