mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-01-26 15:35:48 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
400db0402a | ||
|
|
f0ae178183 |
@@ -57,7 +57,7 @@ function build() {
|
||||
execFileName="${execFileName}.exe"
|
||||
fi
|
||||
go mod tidy
|
||||
CGO_ENABLE=0 GOOS=${os} GOARCH=${arch} go build -ldflags=-w -o ${execFileName} main.go
|
||||
CGO_ENABLE=0 GOOS=${os} GOARCH=${arch} go build -trimpath -ldflags=-w -o ${execFileName} main.go
|
||||
|
||||
if [ -d ${toFolder} ] ; then
|
||||
echo_green "The desired folder already exists. Clear the folder"
|
||||
|
||||
@@ -13,9 +13,6 @@ export function getBaseApiUrl() {
|
||||
const config = {
|
||||
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.10.8',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<span class="logo-title">
|
||||
{{ `${themeConfig.globalTitle}` }}
|
||||
<sub
|
||||
><span style="font-size: 10px; color: goldenrod">{{ ` ${config.version}` }}</span></sub
|
||||
><span style="font-size: 10px; color: goldenrod">{{ ` ${themeConfig.version}` }}</span></sub
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
@@ -17,7 +17,6 @@
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import config from '@/common/config';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
appSlogan: 'common.appSlogan',
|
||||
// 网站logo icon, base64编码内容
|
||||
logoIcon: logoIcon,
|
||||
version: 'latest',
|
||||
// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
|
||||
globalI18n: 'zh-cn',
|
||||
// 默认全局组件大小,可选值"<|large|default|small>",默认 ''
|
||||
@@ -153,12 +154,13 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
if (tc) {
|
||||
this.themeConfig = tc;
|
||||
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
||||
} else {
|
||||
getServerConf().then((res) => {
|
||||
this.themeConfig.globalI18n = res.i18n;
|
||||
});
|
||||
}
|
||||
|
||||
getServerConf().then((res) => {
|
||||
this.themeConfig.globalI18n = res.i18n;
|
||||
this.themeConfig.version = res.version;
|
||||
});
|
||||
|
||||
// 根据后台系统配置初始化
|
||||
getSysStyleConfig().then((res) => {
|
||||
if (res?.title) {
|
||||
|
||||
1
frontend/src/types/pinia.d.ts
vendored
1
frontend/src/types/pinia.d.ts
vendored
@@ -48,6 +48,7 @@ declare interface ThemeConfigState {
|
||||
globalViceTitle: string;
|
||||
appSlogan: string;
|
||||
logoIcon: string;
|
||||
version: string;
|
||||
globalI18n: string;
|
||||
globalComponentSize: string;
|
||||
terminalTheme: string;
|
||||
|
||||
@@ -486,6 +486,7 @@ const runSql = async (sql: string, remark = '', newTab = false) => {
|
||||
state.execResTabs[i].tableColumn = colAndData.columns.map((x: any) => {
|
||||
return {
|
||||
columnName: x.name,
|
||||
key: x.key,
|
||||
columnType: x.type,
|
||||
show: true,
|
||||
};
|
||||
|
||||
@@ -77,9 +77,7 @@
|
||||
<!-- 排序箭头图标 -->
|
||||
<SvgIcon
|
||||
v-if="
|
||||
column.title == nowSortColumn?.columnName &&
|
||||
!showColumnActions[column.key] &&
|
||||
!columnActionVisible[column.key]
|
||||
column.key == nowSortColumn?.key && !showColumnActions[column.key] && !columnActionVisible[column.key]
|
||||
"
|
||||
:color="'var(--el-color-primary)'"
|
||||
:name="nowSortColumn?.order == 'asc' ? 'top' : 'bottom'"
|
||||
@@ -135,7 +133,7 @@
|
||||
<div v-else @dblclick="onEnterEditMode(rowData, column, rowIndex, columnIndex)">
|
||||
<div v-if="canEdit(rowIndex, columnIndex)">
|
||||
<ColumnFormItem
|
||||
v-model="rowData[column.dataKey!]"
|
||||
v-model="rowData[column.key!]"
|
||||
:data-type="column.dataType"
|
||||
@blur="onExitEditMode(rowData, column, rowIndex)"
|
||||
:column-name="column.columnName"
|
||||
@@ -143,11 +141,11 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active ml-0.5 mr-0.5' : 'ml-0.5 mr-0.5'">
|
||||
<span v-if="rowData[column.dataKey!] === null" style="color: var(--el-color-info-light-5)"> NULL </span>
|
||||
<div v-else :class="isUpdated(rowIndex, column.key) ? 'update_field_active ml-0.5 mr-0.5' : 'ml-0.5 mr-0.5'">
|
||||
<span v-if="rowData[column.key!] === null" style="color: var(--el-color-info-light-5)"> NULL </span>
|
||||
|
||||
<span v-else :title="rowData[column.dataKey!]" class="el-text el-text--small is-truncated">
|
||||
{{ rowData[column.dataKey!] }}
|
||||
<span v-else :title="rowData[column.key!]" class="el-text el-text--small is-truncated">
|
||||
{{ rowData[column.key!] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,7 +273,7 @@ const columnActionVisible = ref({} as any);
|
||||
const cmDataCopyCell = new ContextmenuItem('copyValue', 'common.copy')
|
||||
.withIcon('CopyDocument')
|
||||
.withOnClick(async (data: any) => {
|
||||
await copyToClipboard(data.rowData[data.column.dataKey]);
|
||||
await copyToClipboard(data.rowData[data.column.key]);
|
||||
})
|
||||
.withHideFunc(() => {
|
||||
// 选中多条则隐藏该复制按钮
|
||||
@@ -409,7 +407,6 @@ const dbConfig = useStorage('dbConfig', DbThemeConfig);
|
||||
const rowNoColumn = {
|
||||
title: 'No.',
|
||||
key: 'tableDataRowNo',
|
||||
dataKey: 'tableDataRowNo',
|
||||
width: 45,
|
||||
fixed: true,
|
||||
align: 'center',
|
||||
@@ -515,8 +512,6 @@ const setTableColumns = (columns: any) => {
|
||||
x.remark = `${x.columnType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
|
||||
return {
|
||||
...x,
|
||||
key: columnName,
|
||||
dataKey: columnName,
|
||||
width: DbInst.flexColumnWidth(columnName, state.datas),
|
||||
title: columnName,
|
||||
align: x.dataType == DataType.Number ? 'right' : 'left',
|
||||
@@ -565,21 +560,21 @@ const hideColumnAction = () => {
|
||||
const handleColumnCommand = (column: any, command: string) => {
|
||||
switch (command) {
|
||||
case 'sort-asc':
|
||||
onTableSortChange({ columnName: column.dataKey, order: 'asc' });
|
||||
onTableSortChange({ key: column.key, order: 'asc' });
|
||||
break;
|
||||
case 'sort-desc':
|
||||
onTableSortChange({ columnName: column.dataKey, order: 'desc' });
|
||||
onTableSortChange({ key: column.key, order: 'desc' });
|
||||
break;
|
||||
case 'fix':
|
||||
state.columns.forEach((col: any) => {
|
||||
if (col.dataKey == column.dataKey) {
|
||||
if (col.key == column.key) {
|
||||
col.fixed = true;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'unfix':
|
||||
state.columns.forEach((col: any) => {
|
||||
if (col.dataKey == column.dataKey) {
|
||||
if (col.key == column.key) {
|
||||
col.fixed = false;
|
||||
}
|
||||
});
|
||||
@@ -718,7 +713,7 @@ const onGenerateJson = async () => {
|
||||
let obj: any = {};
|
||||
for (let column of state.columns) {
|
||||
if (column.show) {
|
||||
obj[column.title] = selectionData[column.dataKey];
|
||||
obj[column.title] = selectionData[column.key];
|
||||
}
|
||||
}
|
||||
jsonObj.push(obj);
|
||||
@@ -775,7 +770,7 @@ const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex =
|
||||
nowUpdateCell.value = {
|
||||
rowIndex: rowIndex,
|
||||
colIndex: columnIndex,
|
||||
oldValue: rowData[column.dataKey],
|
||||
oldValue: rowData[column.key],
|
||||
dataType: column.dataType,
|
||||
};
|
||||
};
|
||||
@@ -785,7 +780,7 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
return;
|
||||
}
|
||||
const oldValue = nowUpdateCell.value.oldValue;
|
||||
const newValue = rowData[column.dataKey];
|
||||
const newValue = rowData[column.key];
|
||||
|
||||
// 未改变单元格值
|
||||
if (oldValue == newValue) {
|
||||
@@ -800,7 +795,7 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
cellUpdateMap.value.set(rowIndex, updatedRow);
|
||||
}
|
||||
|
||||
const columnName = column.dataKey;
|
||||
const columnName = column.key;
|
||||
let cellData = updatedRow.columnsMap.get(columnName);
|
||||
if (cellData) {
|
||||
// 多次修改情况,可能又修改回原值,则移除该修改单元格
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
<el-text
|
||||
id="copyValue"
|
||||
style="color: var(--el-color-info-light-3)"
|
||||
class="is-truncated !text-[12px] mt-1"
|
||||
class="is-truncated text-[12px]! mt-1"
|
||||
@click="copyToClipboard(sql)"
|
||||
:title="sql"
|
||||
>{{ sql }}</el-text
|
||||
@@ -392,6 +392,7 @@ const selectData = async () => {
|
||||
const columns = await getNowDbInst().loadColumns(props.dbName, props.tableName);
|
||||
columns.forEach((x: any) => {
|
||||
x.show = true;
|
||||
x.key = x.columnName;
|
||||
});
|
||||
state.columns = columns;
|
||||
}
|
||||
@@ -592,7 +593,7 @@ const onSelectByCondition = async () => {
|
||||
*/
|
||||
const onTableSortChange = async (sort: any) => {
|
||||
const sortType = sort.order == 'desc' ? 'DESC' : 'ASC';
|
||||
state.orderBy = `ORDER BY ${state.dbDialect.quoteIdentifier(sort.columnName)} ${sortType}`;
|
||||
state.orderBy = `ORDER BY ${state.dbDialect.quoteIdentifier(sort.key)} ${sortType}`;
|
||||
await onRefresh();
|
||||
};
|
||||
|
||||
|
||||
@@ -85,12 +85,18 @@ const NodeTypeAuthCert = new NodeType(12)
|
||||
(await node.ctx?.addResourceComponent(MachineOpComp)).openTerminal(node.params);
|
||||
})
|
||||
.withContextMenuItems([
|
||||
new ContextmenuItem('term', 'machine.openTerminal').withIcon('Monitor').withOnClick(async (node: TagTreeNode) => {
|
||||
(await node.ctx?.addResourceComponent(MachineOpComp))?.openTerminal(node.params);
|
||||
}),
|
||||
new ContextmenuItem('term-ex', 'machine.newTabOpenTerminal').withIcon('Monitor').withOnClick(async (node: TagTreeNode) => {
|
||||
(await node.ctx?.addResourceComponent(MachineOpComp))?.openTerminal(node.params, true);
|
||||
}),
|
||||
new ContextmenuItem('term', 'machine.openTerminal')
|
||||
.withIcon('Monitor')
|
||||
.withPermission('machine:terminal')
|
||||
.withOnClick(async (node: TagTreeNode) => {
|
||||
(await node.ctx?.addResourceComponent(MachineOpComp))?.openTerminal(node.params);
|
||||
}),
|
||||
new ContextmenuItem('term-ex', 'machine.newTabOpenTerminal')
|
||||
.withIcon('Monitor')
|
||||
.withPermission('machine:terminal')
|
||||
.withOnClick(async (node: TagTreeNode) => {
|
||||
(await node.ctx?.addResourceComponent(MachineOpComp))?.openTerminal(node.params, true);
|
||||
}),
|
||||
new ContextmenuItem('files', 'machine.fileManage').withIcon('FolderOpened').withOnClick(async (node: any) => {
|
||||
(await node.ctx?.addResourceComponent(MachineOpComp)).showFileManage(node.params);
|
||||
}),
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<template v-for="item in contextMenuItems" :key="item.clickId">
|
||||
<el-dropdown-item v-if="!item.isHide(props.data)" :command="item">
|
||||
<el-dropdown-item v-if="!item.isHide(props.data) && hasPerm(item.permission)" :command="item">
|
||||
<SvgIcon v-if="item.icon" :name="item.icon" class="mr-1" />{{ $t(item.txt) }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
@@ -54,6 +54,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { ResourceOpCtx, TagTreeNode } from '@/views/ops/component/tag';
|
||||
import { ResourceOpCtxKey } from '@/views/ops/resource/resource';
|
||||
import { hasPerm } from '@/components/auth/auth';
|
||||
|
||||
const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey, undefined);
|
||||
|
||||
|
||||
@@ -18,20 +18,19 @@ jwt:
|
||||
expire-time: 720
|
||||
# refreshToken过期时间单位分钟
|
||||
refresh-token-expire-time: 4320
|
||||
# 资源密码aes加密key
|
||||
aes:
|
||||
key: 1111111111111111
|
||||
# 若存在mysql配置,优先使用mysql
|
||||
mysql:
|
||||
host: mysql:3306
|
||||
# 数据库配置,dialect支持mysql、sqlite
|
||||
db:
|
||||
dialect: mysql
|
||||
address: mysql:3306
|
||||
name: mayfly-go
|
||||
username: root
|
||||
password: 111049
|
||||
db-name: mayfly-go
|
||||
config: charset=utf8&loc=Local&parseTime=true
|
||||
max-idle-conns: 5
|
||||
sqlite:
|
||||
path: ./mayfly-go.sqlite
|
||||
max-idle-conns: 5
|
||||
# db:
|
||||
# dialect: sqlite
|
||||
# address: ./mayfly-go.db
|
||||
# max-idle-conns: 5
|
||||
# 若同时部署多台机器,则需要配置redis信息用于缓存权限码、验证码、公私钥等
|
||||
# redis:
|
||||
# host: localhost
|
||||
@@ -55,3 +54,6 @@ log:
|
||||
# max-age: 60
|
||||
# # 是否使用 gzip 压缩方式压缩轮转后的日志文件
|
||||
# compress: true
|
||||
# 资源密码aes加密key
|
||||
aes:
|
||||
key: 1111111111111111
|
||||
@@ -1,13 +1,13 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/ai/api"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 注册AI模块的IoC组件
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
api.InitIoc()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/auth/api"
|
||||
"mayfly-go/internal/auth/application"
|
||||
"mayfly-go/internal/auth/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/common/api"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
api.InitIoc()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
@@ -58,6 +59,7 @@ func (d *DbConn) Ping() error {
|
||||
// 执行数据库查询返回的列信息
|
||||
type QueryColumn struct {
|
||||
Name string `json:"name"` // 列名
|
||||
Key string `json:"key"` // 列唯一标识
|
||||
Type string `json:"type"` // 数据类型
|
||||
|
||||
DbDataType *DbDataType `json:"-"`
|
||||
@@ -67,6 +69,7 @@ type QueryColumn struct {
|
||||
func NewQueryColumn(colName string, columnType *DbDataType) *QueryColumn {
|
||||
return &QueryColumn{
|
||||
Name: colName,
|
||||
Key: colName,
|
||||
Type: columnType.DataType.Name,
|
||||
DbDataType: columnType,
|
||||
valuer: columnType.DataType.Valuer(),
|
||||
@@ -245,7 +248,12 @@ func (d *DbConn) walkQueryRows(ctx context.Context, selectSql string, walkFn Wal
|
||||
rowData := make(map[string]any, lenCols)
|
||||
// 把values中的数据复制到row中
|
||||
for i := range scans {
|
||||
rowData[cols[i].Name] = cols[i].value()
|
||||
colname := cols[i].Name
|
||||
if _, e := rowData[colname]; e {
|
||||
colname = colname + strconv.Itoa(i)
|
||||
cols[i].Key = colname
|
||||
}
|
||||
rowData[colname] = cols[i].value()
|
||||
}
|
||||
if err = walkFn(rowData, cols); err != nil {
|
||||
logx.ErrorfContext(ctx, "[%s] cursor traversal query result set error, exit traversal: %s", selectSql, err.Error())
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/db/ai/tools"
|
||||
"mayfly-go/internal/db/api"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(application.Init)
|
||||
initialize.AddTerminateFunc(Terminate)
|
||||
starter.AddInitFunc(application.Init)
|
||||
starter.AddTerminateFunc(Terminate)
|
||||
// 注册AI数据库工具
|
||||
tools.Init()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/docker/api"
|
||||
"mayfly-go/internal/docker/application"
|
||||
"mayfly-go/internal/docker/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/es/api"
|
||||
"mayfly-go/internal/es/application"
|
||||
"mayfly-go/internal/es/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(application.Init)
|
||||
starter.AddInitFunc(application.Init)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/file/api"
|
||||
"mayfly-go/internal/file/application"
|
||||
"mayfly-go/internal/file/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/flow/api"
|
||||
"mayfly-go/internal/flow/application"
|
||||
"mayfly-go/internal/flow/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(application.Init)
|
||||
starter.AddInitFunc(application.Init)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/machine/api"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(application.Init)
|
||||
starter.AddInitFunc(application.Init)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/mongo/api"
|
||||
"mayfly-go/internal/mongo/application"
|
||||
"mayfly-go/internal/mongo/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
|
||||
@@ -2,7 +2,6 @@ package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/msg/api"
|
||||
"mayfly-go/internal/msg/application"
|
||||
"mayfly-go/internal/msg/application/dto"
|
||||
@@ -13,16 +12,17 @@ import (
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(Init)
|
||||
starter.AddInitFunc(Init)
|
||||
}
|
||||
|
||||
func Init() {
|
||||
|
||||
@@ -2,7 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/pkg/utils/assert"
|
||||
"mayfly-go/pkg/starter"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
)
|
||||
|
||||
@@ -20,11 +20,15 @@ func (a *Aes) DecryptBase64(data string) ([]byte, error) {
|
||||
return cryptox.AesDecryptBase64(data, []byte(a.Key))
|
||||
}
|
||||
|
||||
func (a *Aes) Valid() {
|
||||
func (a *Aes) ApplyDefaults() error {
|
||||
if a.Key == "" {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
aesKeyLen := len(a.Key)
|
||||
assert.IsTrue(aesKeyLen == 16 || aesKeyLen == 24 || aesKeyLen == 32,
|
||||
fmt.Sprintf("config.yml之 [aes.key] 长度需为16、24、32位长度, 当前为%d位", aesKeyLen))
|
||||
|
||||
if aesKeyLen != 16 && aesKeyLen != 24 && aesKeyLen != 32 {
|
||||
return starter.NewConfigError("aes.key", fmt.Sprintf("长度需为16、24、32位长度, 当前为%d位", aesKeyLen))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
AppName = "mayfly-go"
|
||||
Version = "v1.10.8"
|
||||
Version = "v1.10.9"
|
||||
)
|
||||
|
||||
func GetAppInfo() string {
|
||||
return fmt.Sprintf("[%s:%s]", AppName, Version)
|
||||
}
|
||||
|
||||
@@ -4,24 +4,17 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/starter"
|
||||
"mayfly-go/pkg/utils/ymlx"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigItem interface {
|
||||
// 验证配置
|
||||
Valid()
|
||||
|
||||
// 如果不存在配置值,则设置默认值
|
||||
Default()
|
||||
}
|
||||
|
||||
// 配置文件映射对象
|
||||
var Conf *Config
|
||||
|
||||
func Init() {
|
||||
func Init() (*Config, error) {
|
||||
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
|
||||
flag.Parse()
|
||||
// 获取启动参数中,配置文件的绝对路径
|
||||
@@ -35,10 +28,12 @@ func Init() {
|
||||
// 尝试使用系统环境变量替换配置信息
|
||||
yc.ReplaceOsEnv()
|
||||
|
||||
yc.IfBlankDefaultValue()
|
||||
// 校验配置文件内容信息
|
||||
yc.Valid()
|
||||
if err := yc.ApplyDefaults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Conf = yc
|
||||
return yc, nil
|
||||
}
|
||||
|
||||
// 启动配置参数
|
||||
@@ -48,39 +43,23 @@ type CmdConfigParam struct {
|
||||
|
||||
// yaml配置文件映射对象
|
||||
type Config struct {
|
||||
Server Server `yaml:"server"`
|
||||
Jwt Jwt `yaml:"jwt"`
|
||||
Aes Aes `yaml:"aes"`
|
||||
Mysql Mysql `yaml:"mysql"`
|
||||
Sqlite Sqlite `yaml:"sqlite"`
|
||||
Redis Redis `yaml:"redis"`
|
||||
Log Log `yaml:"log"`
|
||||
starter.Conf `yaml:",inline"`
|
||||
|
||||
Aes Aes `yaml:"aes"`
|
||||
}
|
||||
|
||||
func (c *Config) IfBlankDefaultValue() {
|
||||
c.Log.Default()
|
||||
// 优先初始化log,因为后续的一些default方法中会需要用到。统一日志输出
|
||||
logx.Init(logx.Config{
|
||||
Level: c.Log.Level,
|
||||
Type: c.Log.Type,
|
||||
AddSource: c.Log.AddSource,
|
||||
Filename: c.Log.File.Name,
|
||||
Filepath: c.Log.File.Path,
|
||||
MaxSize: c.Log.File.MaxSize,
|
||||
MaxAge: c.Log.File.MaxAge,
|
||||
Compress: c.Log.File.Compress,
|
||||
})
|
||||
var _ starter.ConfigItem = (*Config)(nil)
|
||||
|
||||
c.Server.Default()
|
||||
c.Jwt.Default()
|
||||
c.Mysql.Default()
|
||||
c.Sqlite.Default()
|
||||
}
|
||||
func (c *Config) ApplyDefaults() error {
|
||||
if err := c.Conf.ApplyDefaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 配置文件内容校验
|
||||
func (c *Config) Valid() {
|
||||
c.Jwt.Valid()
|
||||
c.Aes.Valid()
|
||||
if err := c.Aes.ApplyDefaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 替换系统环境变量,如果环境变量中存在该值,则优先使用环境变量设定的值
|
||||
@@ -96,27 +75,29 @@ func (c *Config) ReplaceOsEnv() {
|
||||
|
||||
dbHost := os.Getenv("MAYFLY_DB_HOST")
|
||||
if dbHost != "" {
|
||||
c.Mysql.Host = dbHost
|
||||
c.DB.Address = dbHost
|
||||
c.DB.Dialect = starter.DialectMySQL
|
||||
}
|
||||
|
||||
dbName := os.Getenv("MAYFLY_DB_NAME")
|
||||
if dbName != "" {
|
||||
c.Mysql.Dbname = dbName
|
||||
c.DB.Name = dbName
|
||||
}
|
||||
|
||||
dbUser := os.Getenv("MAYFLY_DB_USER")
|
||||
if dbUser != "" {
|
||||
c.Mysql.Username = dbUser
|
||||
c.DB.Username = dbUser
|
||||
}
|
||||
|
||||
dbPwd := os.Getenv("MAYFLY_DB_PASS")
|
||||
if dbPwd != "" {
|
||||
c.Mysql.Password = dbPwd
|
||||
c.DB.Password = dbPwd
|
||||
}
|
||||
|
||||
sqlitePath := os.Getenv("MAYFLY_SQLITE_PATH")
|
||||
if sqlitePath != "" {
|
||||
c.Sqlite.Path = sqlitePath
|
||||
c.DB.Address = sqlitePath
|
||||
c.DB.Dialect = starter.DialectSQLite
|
||||
}
|
||||
|
||||
aesKey := os.Getenv("MAYFLY_AES_KEY")
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/assert"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
)
|
||||
|
||||
type Jwt struct {
|
||||
Key string `yaml:"key"`
|
||||
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
||||
RefreshTokenExpireTime uint64 `yaml:"refresh-token-expire-time"` // 刷新token的过期时间,单位分钟
|
||||
}
|
||||
|
||||
func (j *Jwt) Default() {
|
||||
if j.Key == "" {
|
||||
// 如果配置文件中的jwt key为空,则随机生成字符串
|
||||
j.Key = stringx.Rand(32)
|
||||
logx.Warnf("未配置jwt.key, 随机生成key为: %s", j.Key)
|
||||
}
|
||||
|
||||
if j.ExpireTime == 0 {
|
||||
j.ExpireTime = 1440
|
||||
logx.Warnf("未配置jwt.expire-time, 默认值: %d", j.ExpireTime)
|
||||
}
|
||||
if j.RefreshTokenExpireTime == 0 {
|
||||
j.RefreshTokenExpireTime = j.ExpireTime * 5
|
||||
logx.Warnf("未配置jwt.refresh-token-expire-time, 默认值: %d", j.RefreshTokenExpireTime)
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Jwt) Valid() {
|
||||
assert.IsTrue(j.ExpireTime != 0, "config.yml之[jwt.expire-time] 不能为空")
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/logx"
|
||||
)
|
||||
|
||||
type Log struct {
|
||||
Level string `yaml:"level"`
|
||||
Type string `yaml:"type"`
|
||||
AddSource bool `yaml:"add-source"`
|
||||
File LogFile `yaml:"file"`
|
||||
}
|
||||
|
||||
func (l *Log) Default() {
|
||||
if l.Level == "" {
|
||||
l.Level = "info"
|
||||
logx.Warnf("未配置log.level, 默认值: %s", l.Level)
|
||||
}
|
||||
if l.Type == "" {
|
||||
l.Type = "text"
|
||||
}
|
||||
if l.File.Name == "" {
|
||||
l.File.Name = "mayfly-go.log"
|
||||
}
|
||||
}
|
||||
|
||||
type LogFile struct {
|
||||
Name string `yaml:"name"`
|
||||
Path string `yaml:"path"`
|
||||
MaxSize int `yaml:"max-size"`
|
||||
MaxAge int `yaml:"max-age"`
|
||||
Compress bool `yaml:"compress"`
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package config
|
||||
|
||||
import "mayfly-go/pkg/logx"
|
||||
|
||||
type Mysql struct {
|
||||
Host string `mapstructure:"path" json:"host" yaml:"host"`
|
||||
Config string `mapstructure:"config" json:"config" yaml:"config"`
|
||||
Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`
|
||||
Username string `mapstructure:"username" json:"username" yaml:"username"`
|
||||
Password string `mapstructure:"password" json:"password" yaml:"password"`
|
||||
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"`
|
||||
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"`
|
||||
LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`
|
||||
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
|
||||
}
|
||||
|
||||
func (m *Mysql) Default() {
|
||||
if m.Host == "" {
|
||||
m.Host = "localhost:3306"
|
||||
logx.Warnf("[使用sqlite可忽略]未配置mysql.host, 默认值: %s", m.Host)
|
||||
}
|
||||
if m.Config == "" {
|
||||
m.Config = "charset=utf8&loc=Local&parseTime=true"
|
||||
}
|
||||
if m.MaxIdleConns == 0 {
|
||||
m.MaxIdleConns = 5
|
||||
}
|
||||
if m.MaxOpenConns == 0 {
|
||||
m.MaxOpenConns = m.MaxIdleConns
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mysql) Dsn() string {
|
||||
return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package config
|
||||
|
||||
type Redis struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Password string `yaml:"password"`
|
||||
Db int `yaml:"db"`
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/pkg/i18n"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Lang string `yaml:"lang"`
|
||||
Port int `yaml:"port"`
|
||||
Model string `yaml:"model"`
|
||||
ContextPath string `yaml:"context-path"` // 请求路径上下文
|
||||
Cors bool `yaml:"cors"`
|
||||
Tls *Tls `yaml:"tls"`
|
||||
Static *[]*Static `yaml:"static"`
|
||||
StaticFile *[]*StaticFile `yaml:"static-file"`
|
||||
}
|
||||
|
||||
func (s *Server) Default() {
|
||||
if s.Lang == "" {
|
||||
s.Lang = i18n.Zh_CN
|
||||
}
|
||||
if s.Model == "" {
|
||||
s.Model = "release"
|
||||
}
|
||||
if s.Port == 0 {
|
||||
s.Port = 18888
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) GetPort() string {
|
||||
return fmt.Sprintf(":%d", s.Port)
|
||||
}
|
||||
|
||||
type Static struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Root string `yaml:"root"`
|
||||
}
|
||||
|
||||
type StaticFile struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Filepath string `yaml:"filepath"`
|
||||
}
|
||||
|
||||
type Tls struct {
|
||||
Enable bool `yaml:"enable"` // 是否启用tls
|
||||
KeyFile string `yaml:"key-file"` // 私钥文件路径
|
||||
CertFile string `yaml:"cert-file"` // 证书文件路径
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package config
|
||||
|
||||
import "mayfly-go/pkg/logx"
|
||||
|
||||
type Sqlite struct {
|
||||
Path string `mapstructure:"path" json:"path" yaml:"path"`
|
||||
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"`
|
||||
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"`
|
||||
}
|
||||
|
||||
func (m *Sqlite) Default() {
|
||||
if m.Path == "" {
|
||||
m.Path = "./mayfly-go.sqlite"
|
||||
logx.Warnf("[使用mysql可忽略]未配置sqlite.path, 默认值: %s", m.Path)
|
||||
}
|
||||
if m.MaxIdleConns == 0 {
|
||||
m.MaxIdleConns = 5
|
||||
}
|
||||
if m.MaxOpenConns == 0 {
|
||||
m.MaxOpenConns = m.MaxIdleConns
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/pkg/config"
|
||||
"mayfly-go/pkg/logx"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func printBanner() {
|
||||
buildInfo, _ := debug.ReadBuildInfo()
|
||||
logx.Print(fmt.Sprintf(`
|
||||
__ _
|
||||
_ __ ___ __ _ _ _ / _| |_ _ __ _ ___
|
||||
| '_ ' _ \ / _' | | | | |_| | | | |_____ / _' |/ _ \
|
||||
| | | | | | (_| | |_| | _| | |_| |_____| (_| | (_) | version: %s | go_version: %s | pid: %d
|
||||
|_| |_| |_|\__,_|\__, |_| |_|\__, | \__, |\___/
|
||||
|___/ |___/ |___/ `, config.Version, buildInfo.GoVersion, os.Getpid()))
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/pkg/config"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/rediscli"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// 有配置redis信息,则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等信息
|
||||
func initCache() {
|
||||
redisCli := connRedis()
|
||||
|
||||
if redisCli == nil {
|
||||
logx.Info("no redis configuration, using local cache")
|
||||
return
|
||||
}
|
||||
|
||||
logx.Info("redis connected successfully, using Redis for caching")
|
||||
rediscli.SetCli(connRedis())
|
||||
cache.SetCache(cache.NewRedisCache(redisCli))
|
||||
}
|
||||
|
||||
func connRedis() *redis.Client {
|
||||
// 设置redis客户端
|
||||
redisConf := config.Conf.Redis
|
||||
if redisConf.Host == "" {
|
||||
// logx.Panic("未找到redis配置信息")
|
||||
return nil
|
||||
}
|
||||
logx.Infof("redis connecting [%s:%d]", redisConf.Host, redisConf.Port)
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", redisConf.Host, redisConf.Port),
|
||||
Password: redisConf.Password, // no password set
|
||||
DB: redisConf.Db, // use default DB
|
||||
})
|
||||
// 测试连接
|
||||
_, e := rdb.Ping(context.TODO()).Result()
|
||||
if e != nil {
|
||||
logx.Panicf("redis connection faild! [%s:%d][%s]", redisConf.Host, redisConf.Port, e.Error())
|
||||
}
|
||||
return rdb
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mayfly-go/internal/pkg/config"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/logx"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func initDb() {
|
||||
global.Db = initGormDb()
|
||||
}
|
||||
|
||||
func initGormDb() *gorm.DB {
|
||||
m := config.Conf.Mysql
|
||||
// 存在msyql数据库名,则优先使用mysql
|
||||
if m.Dbname != "" {
|
||||
return initMysql(m)
|
||||
}
|
||||
|
||||
return initSqlite(config.Conf.Sqlite)
|
||||
}
|
||||
|
||||
func initMysql(m config.Mysql) *gorm.DB {
|
||||
logx.Infof("connecting to mysql [%s]", m.Host)
|
||||
mysqlConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
|
||||
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
|
||||
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), getGormConfig()); err != nil {
|
||||
logx.Panicf("failed to connect to mysql! [%s]", err.Error())
|
||||
return nil
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
|
||||
|
||||
// 如果是开发环境时,打印sql语句
|
||||
if logx.GetConfig().IsDebug() {
|
||||
db = db.Debug()
|
||||
}
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func initSqlite(sc config.Sqlite) *gorm.DB {
|
||||
logx.Infof("connecting to sqlite [%s]", sc.Path)
|
||||
if db, err := gorm.Open(sqlite.Open(sc.Path), getGormConfig()); err != nil {
|
||||
logx.Panicf("failed to connect to sqlite! [%s]", err.Error())
|
||||
return nil
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(sc.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(sc.MaxOpenConns)
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func getGormConfig() *gorm.Config {
|
||||
sqlLogLevel := logger.Error
|
||||
logConf := logx.GetConfig()
|
||||
// 如果为配置文件中配置的系统日志级别为debug,则打印gorm执行的sql信息
|
||||
if logConf.IsDebug() {
|
||||
sqlLogLevel = logger.Info
|
||||
}
|
||||
|
||||
gormLogger := logger.New(
|
||||
log.New(logConf.GetLogOut(), "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // 慢 SQL 阈值
|
||||
LogLevel: sqlLogLevel, // 日志级别, 改为logger.Info即可显示sql语句
|
||||
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
|
||||
Colorful: false, // 禁用彩色打印
|
||||
},
|
||||
)
|
||||
|
||||
return &gorm.Config{NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
}, Logger: gormLogger}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/pkg/config"
|
||||
"mayfly-go/migration"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/validatorx"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func RunWebServer() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// 初始化config.yml配置文件映射信息或使用环境变量。并初始化系统日志相关配置
|
||||
config.Init()
|
||||
|
||||
// 打印banner
|
||||
printBanner()
|
||||
|
||||
// 初始化并赋值数据库全局变量
|
||||
initDb()
|
||||
|
||||
// 初始化缓存
|
||||
initCache()
|
||||
|
||||
// 数据库升级操作
|
||||
if err := migration.RunMigrations(global.Db); err != nil {
|
||||
logx.Panicf("db migration failed: %v", err)
|
||||
}
|
||||
|
||||
// 参数校验器初始化、如错误提示中文转译等
|
||||
validatorx.Init()
|
||||
// 注册自定义正则表达式校验规则
|
||||
RegisterCustomPatterns()
|
||||
|
||||
// 初始化其他需要启动时运行的方法
|
||||
initialize.InitOther()
|
||||
|
||||
// 运行web服务
|
||||
runWebServer(ctx)
|
||||
}
|
||||
|
||||
// 注册自定义正则表达式校验规则
|
||||
func RegisterCustomPatterns() {
|
||||
// 账号用户名校验
|
||||
validatorx.RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线")
|
||||
validatorx.RegisterPattern("resource_code", "^[a-zA-Z0-9_\\-.:]{1,32}$", "只允许输入1-32位大小写字母、数字、_-.:")
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/pkg/config"
|
||||
"mayfly-go/pkg/gox"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/middleware"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/static"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func runWebServer(ctx context.Context) {
|
||||
// 设置gin日志输出器
|
||||
logOut := logx.GetConfig().GetLogOut()
|
||||
gin.DefaultErrorWriter = logOut
|
||||
gin.DefaultWriter = logOut
|
||||
|
||||
// 权限处理器
|
||||
req.UseBeforeHandlerInterceptor(req.PermissionHandler)
|
||||
// 日志处理器
|
||||
req.UseAfterHandlerInterceptor(req.LogHandler)
|
||||
// 设置日志保存函数
|
||||
req.SetSaveLogFunc(sysapp.GetSyslogApp().SaveFromReq)
|
||||
|
||||
// jwt配置
|
||||
jwtConf := config.Conf.Jwt
|
||||
req.SetJwtConf(req.JwtConf{
|
||||
Key: jwtConf.Key,
|
||||
ExpireTime: jwtConf.ExpireTime,
|
||||
RefreshTokenExpireTime: jwtConf.RefreshTokenExpireTime,
|
||||
})
|
||||
|
||||
// i18n配置
|
||||
i18n.SetLang(config.Conf.Server.Lang)
|
||||
|
||||
// server配置
|
||||
serverConfig := config.Conf.Server
|
||||
gin.SetMode(serverConfig.Model)
|
||||
|
||||
var router = gin.New()
|
||||
router.MaxMultipartMemory = 8 << 20
|
||||
// 初始化接口路由
|
||||
initialize.InitRouter(router, initialize.RouterConfig{ContextPath: serverConfig.ContextPath})
|
||||
// 设置静态资源
|
||||
setStatic(serverConfig.ContextPath, router)
|
||||
// 是否允许跨域
|
||||
if serverConfig.Cors {
|
||||
router.Use(middleware.Cors())
|
||||
}
|
||||
|
||||
srv := http.Server{
|
||||
Addr: config.Conf.Server.GetPort(),
|
||||
// 注册路由
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer gox.Recover()
|
||||
<-ctx.Done()
|
||||
logx.Info("Shutdown HTTP Server ...")
|
||||
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
err := srv.Shutdown(timeout)
|
||||
if err != nil {
|
||||
logx.Errorf("failed to Shutdown HTTP Server: %v", err)
|
||||
}
|
||||
|
||||
initialize.Terminate()
|
||||
}()
|
||||
|
||||
confSrv := config.Conf.Server
|
||||
logx.Infof("Listening and serving HTTP on %s", srv.Addr+confSrv.ContextPath)
|
||||
var err error
|
||||
if confSrv.Tls != nil && confSrv.Tls.Enable {
|
||||
err = srv.ListenAndServeTLS(confSrv.Tls.CertFile, confSrv.Tls.KeyFile)
|
||||
} else {
|
||||
err = srv.ListenAndServe()
|
||||
}
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
logx.Info("HTTP Server Shutdown")
|
||||
} else if err != nil {
|
||||
logx.Errorf("Failed to Start HTTP Server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setStatic(contextPath string, router *gin.Engine) {
|
||||
// 使用embed打包静态资源至二进制文件中
|
||||
fsys, _ := fs.Sub(static.Static, "static")
|
||||
fileServer := http.FileServer(http.FS(fsys))
|
||||
handler := WrapStaticHandler(http.StripPrefix(contextPath, fileServer))
|
||||
|
||||
router.GET(contextPath+"/", handler)
|
||||
router.GET(contextPath+"/favicon.ico", handler)
|
||||
router.GET(contextPath+"/config.js", handler)
|
||||
// 所有/assets/**开头的都是静态资源文件
|
||||
router.GET(contextPath+"/assets/*file", handler)
|
||||
|
||||
// 设置静态资源
|
||||
if staticConfs := config.Conf.Server.Static; staticConfs != nil {
|
||||
for _, scs := range *staticConfs {
|
||||
router.StaticFS(scs.RelativePath, http.Dir(scs.Root))
|
||||
}
|
||||
}
|
||||
// 设置静态文件
|
||||
if staticFileConfs := config.Conf.Server.StaticFile; staticFileConfs != nil {
|
||||
for _, sfs := range *staticFileConfs {
|
||||
router.StaticFile(sfs.RelativePath, sfs.Filepath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WrapStaticHandler(h http.Handler) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Cache-Control", `public, max-age=31536000`)
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/redis/api"
|
||||
"mayfly-go/internal/redis/application"
|
||||
"mayfly-go/internal/redis/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(application.Init)
|
||||
starter.AddInitFunc(application.Init)
|
||||
}
|
||||
|
||||
@@ -63,5 +63,5 @@ func (c *Config) SaveConfig(rc *req.Ctx) {
|
||||
// GetServerConfig 获取当前系统启动配置
|
||||
func (c *Config) GetServerConfig(rc *req.Ctx) {
|
||||
conf := config.Conf
|
||||
rc.ResData = collx.Kvs("i18n", conf.Server.Lang)
|
||||
rc.ResData = collx.Kvs("i18n", conf.Server.Lang, "version", config.Version)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/sys/api"
|
||||
"mayfly-go/internal/sys/application"
|
||||
"mayfly-go/internal/sys/infra/persistence"
|
||||
"mayfly-go/pkg/starter"
|
||||
"mayfly-go/pkg/validatorx"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
// 账号用户名校验
|
||||
validatorx.RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线")
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@ package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
"mayfly-go/internal/tag/api"
|
||||
"mayfly-go/internal/tag/application"
|
||||
"mayfly-go/internal/tag/infra/persistence"
|
||||
"mayfly-go/pkg/eventbus"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
starter.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
|
||||
initialize.AddInitFunc(Init)
|
||||
starter.AddInitFunc(Init)
|
||||
}
|
||||
|
||||
func Init() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "mayfly-go/internal/ai/init"
|
||||
_ "mayfly-go/internal/auth/init"
|
||||
_ "mayfly-go/internal/common/init"
|
||||
@@ -12,12 +13,53 @@ import (
|
||||
_ "mayfly-go/internal/machine/init"
|
||||
_ "mayfly-go/internal/mongo/init"
|
||||
_ "mayfly-go/internal/msg/init"
|
||||
"mayfly-go/internal/pkg/starter"
|
||||
"mayfly-go/internal/pkg/config"
|
||||
_ "mayfly-go/internal/redis/init"
|
||||
_ "mayfly-go/internal/sys/init"
|
||||
_ "mayfly-go/internal/tag/init"
|
||||
"mayfly-go/migration"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/starter"
|
||||
"mayfly-go/static"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
starter.RunWebServer()
|
||||
// 初始化config.yml配置文件映射信息或使用环境变量
|
||||
config, err := config.Init()
|
||||
if err != nil {
|
||||
logx.Panicf("config init failed: %v", err)
|
||||
}
|
||||
printBanner()
|
||||
|
||||
if err = starter.Run(config.Conf,
|
||||
starter.WithOnDbReady(func(db *gorm.DB) error {
|
||||
// 数据库升级操作
|
||||
return migration.RunMigrations(db)
|
||||
}),
|
||||
starter.WithLogSaver(func() req.SaveLogFunc {
|
||||
// 日志保存
|
||||
return sysapp.GetSyslogApp().SaveFromReq
|
||||
}),
|
||||
starter.WithStaticRouter(static.Router()),
|
||||
); err != nil {
|
||||
logx.Panicf("starter server failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func printBanner() {
|
||||
buildInfo, _ := debug.ReadBuildInfo()
|
||||
logx.Print(fmt.Sprintf(`
|
||||
__ _
|
||||
_ __ ___ __ _ _ _ / _| |_ _ __ _ ___
|
||||
| '_ ' _ \ / _' | | | | |_| | | | |_____ / _' |/ _ \
|
||||
| | | | | | (_| | |_| | _| | |_| |_____| (_| | (_) | version: %s | go_version: %s | pid: %d
|
||||
|_| |_| |_|\__,_|\__, |_| |_|\__, | \__, |\___/
|
||||
|___/ |___/ |___/ `, config.Version, buildInfo.GoVersion, os.Getpid()))
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func V1_9_3() []*gormigrate.Migration {
|
||||
{
|
||||
ID: "20250213-v1.9.3-addMachineExtra-updateMenuIcon",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
tx.Migrator().AddColumn(&machineentity.Machine{}, "extra")
|
||||
tx.AutoMigrate(&machineentity.Machine{})
|
||||
|
||||
// 更新菜单图标
|
||||
resourceModel := &sysentity.Resource{}
|
||||
|
||||
13
server/pkg/req/router.go
Normal file
13
server/pkg/req/router.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package req
|
||||
|
||||
// RouterApi
|
||||
// 该接口的实现类注册到ioc中,则会自动将请求配置注册到路由中
|
||||
type RouterApi interface {
|
||||
// ReqConfs 获取请求配置信息
|
||||
ReqConfs() *Confs
|
||||
}
|
||||
|
||||
// RouterConfig 请求路由配置
|
||||
type RouterConfig struct {
|
||||
ContextPath string // 请求路径上下文
|
||||
}
|
||||
49
server/pkg/starter/cache.go
Normal file
49
server/pkg/starter/cache.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/rediscli"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// 有配置redis信息,则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等信息
|
||||
func initCache(redisConfig RedisConf) error {
|
||||
redisCli, err := connRedis(redisConfig)
|
||||
|
||||
if redisCli == nil && err == nil {
|
||||
logx.Info("no redis configuration, using local cache")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Info("redis connected successfully, using Redis for caching")
|
||||
rediscli.SetCli(redisCli)
|
||||
cache.SetCache(cache.NewRedisCache(redisCli))
|
||||
return nil
|
||||
}
|
||||
|
||||
func connRedis(redisConfig RedisConf) (*redis.Client, error) {
|
||||
// 设置redis客户端
|
||||
if redisConfig.Host == "" {
|
||||
return nil, nil
|
||||
}
|
||||
logx.Infof("redis connecting [%s:%d]", redisConfig.Host, redisConfig.Port)
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port),
|
||||
Password: redisConfig.Password, // no password set
|
||||
DB: redisConfig.Db, // use default DB
|
||||
})
|
||||
// 测试连接
|
||||
_, e := rdb.Ping(context.TODO()).Result()
|
||||
if e != nil {
|
||||
logx.Errorf("redis connection failed! [%s:%d][%s]", redisConfig.Host, redisConfig.Port, e.Error())
|
||||
}
|
||||
return rdb, e
|
||||
}
|
||||
152
server/pkg/starter/config.go
Normal file
152
server/pkg/starter/config.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
)
|
||||
|
||||
type Conf struct {
|
||||
Server ServerConf `yaml:"server"`
|
||||
DB DBConf `yaml:"db"`
|
||||
Jwt JwtConf `yaml:"jwt"`
|
||||
Log LogConf `yaml:"log"`
|
||||
Redis RedisConf `yaml:"redis"`
|
||||
|
||||
isApplyDefaults bool
|
||||
}
|
||||
|
||||
func (c *Conf) ApplyDefaults() error {
|
||||
if c.isApplyDefaults {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.Log.ApplyDefaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
// 优先初始化log,因为后续的一些配置ApplyDefaults方法中可能会用到
|
||||
logx.Init(logx.Config{
|
||||
Level: c.Log.Level,
|
||||
Type: c.Log.Type,
|
||||
AddSource: c.Log.AddSource,
|
||||
Filename: c.Log.File.Name,
|
||||
Filepath: c.Log.File.Path,
|
||||
MaxSize: c.Log.File.MaxSize,
|
||||
MaxAge: c.Log.File.MaxAge,
|
||||
Compress: c.Log.File.Compress,
|
||||
})
|
||||
|
||||
if err := ApplyConfigDefaults(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.isApplyDefaults = true
|
||||
return nil
|
||||
}
|
||||
|
||||
/************************ server ************************/
|
||||
|
||||
// ServerConf 配置
|
||||
type ServerConf struct {
|
||||
Lang string `yaml:"lang" default:"zh-cn" options:"zh-cn,en"`
|
||||
Port int `yaml:"port" default:"18888"`
|
||||
Model string `yaml:"model" default:"release" options:"release,debug"`
|
||||
ContextPath string `yaml:"context-path"` // 请求路径上下文
|
||||
Cors bool `yaml:"cors"`
|
||||
|
||||
// TLS 配置
|
||||
TLS struct {
|
||||
Enable bool `yaml:"enable"` // 是否启用tls
|
||||
KeyFile string `yaml:"key-file"` // 私钥文件路径
|
||||
CertFile string `yaml:"cert-file"` // 证书文件路径
|
||||
} `yaml:"tls"`
|
||||
|
||||
Statics []struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Root string `yaml:"root"`
|
||||
} `yaml:"statics"`
|
||||
|
||||
StaticFiles []struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Filepath string `yaml:"filepath"`
|
||||
} `yaml:"static-files"`
|
||||
}
|
||||
|
||||
func (s *ServerConf) GetPort() string {
|
||||
return fmt.Sprintf(":%d", s.Port)
|
||||
}
|
||||
|
||||
/************************ db ************************/
|
||||
|
||||
type DbDialect string
|
||||
|
||||
const (
|
||||
DialectMySQL DbDialect = "mysql"
|
||||
DialectSQLite DbDialect = "sqlite"
|
||||
)
|
||||
|
||||
// DBConf 配置
|
||||
type DBConf struct {
|
||||
Dialect DbDialect `yaml:"dialect" default:"sqlite" options:"mysql,sqlite"` // 数据库类型
|
||||
Address string `yaml:"address" default:"mayfly-go.db"` // 地址
|
||||
Name string `yaml:"name"` // 数据库名
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Config string `yaml:"config"` // 额外配置,如 charset=utf8&loc=Local&parseTime=true
|
||||
MaxIdleConns int `yaml:"max-idle-conns" default:"5"`
|
||||
MaxOpenConns int `yaml:"max-open-conns"`
|
||||
}
|
||||
|
||||
/************************ jwt ************************/
|
||||
|
||||
// JwtConf 配置
|
||||
type JwtConf struct {
|
||||
Key string `yaml:"key"`
|
||||
ExpireTime uint64 `yaml:"expire-time" default:"1440"` // 过期时间,单位分钟
|
||||
RefreshTokenExpireTime uint64 `yaml:"refresh-token-expire-time" default:"7200"` // 刷新token的过期时间,单位分钟
|
||||
}
|
||||
|
||||
var _ ConfigItem = (*JwtConf)(nil)
|
||||
|
||||
func (j *JwtConf) ApplyDefaults() error {
|
||||
if j.Key == "" {
|
||||
// 如果配置文件中的jwt key为空,则随机生成字符串
|
||||
j.Key = stringx.Rand(32)
|
||||
LogDefaultValue("jwt.key", j.Key)
|
||||
}
|
||||
|
||||
return ApplyConfigDefaults(j, "jwt")
|
||||
}
|
||||
|
||||
/************************ log ************************/
|
||||
|
||||
// LogConf 配置
|
||||
type LogConf struct {
|
||||
Level string `yaml:"level" default:"info" options:"debug,info,warn,error"`
|
||||
Type string `yaml:"type" default:"text" options:"text,json"`
|
||||
AddSource bool `yaml:"add-source"`
|
||||
|
||||
File struct {
|
||||
Name string `yaml:"name" default:"mayfly-go.log"`
|
||||
Path string `yaml:"path"`
|
||||
MaxSize int `yaml:"max-size"`
|
||||
MaxAge int `yaml:"max-age"`
|
||||
Compress bool `yaml:"compress"`
|
||||
} `yaml:"file"`
|
||||
}
|
||||
|
||||
var _ ConfigItem = (*LogConf)(nil)
|
||||
|
||||
func (l *LogConf) ApplyDefaults() error {
|
||||
return ApplyConfigDefaults(l, "log")
|
||||
}
|
||||
|
||||
/************************ redis ************************/
|
||||
|
||||
// RedisConf 配置
|
||||
type RedisConf struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Password string `yaml:"password"`
|
||||
Db int `yaml:"db"`
|
||||
}
|
||||
218
server/pkg/starter/config_tool.go
Normal file
218
server/pkg/starter/config_tool.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/structx"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConfigItem 配置项
|
||||
type ConfigItem interface {
|
||||
// 如果不存在配置值,则设置默认值,并校验配置项
|
||||
ApplyDefaults() error
|
||||
}
|
||||
|
||||
// ConfigError 配置错误类型
|
||||
type ConfigError struct {
|
||||
Field string // 字段名
|
||||
Message string // 错误描述
|
||||
}
|
||||
|
||||
func (e *ConfigError) Error() string {
|
||||
return fmt.Sprintf("配置错误: [%s] %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
// NewConfigError 创建配置错误
|
||||
func NewConfigError(field, message string) error {
|
||||
return &ConfigError{
|
||||
Field: field,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// LogDefaultValue 记录配置默认值日志
|
||||
func LogDefaultValue(field, defaultValue any) {
|
||||
logx.Warnf("配置项 [%s] 未设置,使用默认值: %v", field, defaultValue)
|
||||
}
|
||||
|
||||
// ApplyConfigDefaults 应用所有配置项的默认值,支持递归处理嵌套结构体和ConfigItem接口
|
||||
func ApplyConfigDefaults(obj any, parentConfigPath ...string) error {
|
||||
parentPath := strings.Join(parentConfigPath, ".")
|
||||
|
||||
configValue := reflect.ValueOf(obj).Elem()
|
||||
configType := configValue.Type()
|
||||
|
||||
for i := 0; i < configValue.NumField(); i++ {
|
||||
field := configValue.Field(i)
|
||||
fieldType := configType.Field(i)
|
||||
|
||||
// 检查字段是否为导出的字段
|
||||
if !field.CanSet() || !field.CanAddr() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果字段是指针类型,需要先创建实例
|
||||
if field.Kind() == reflect.Ptr {
|
||||
if field.IsNil() {
|
||||
newValue := reflect.New(field.Type().Elem())
|
||||
field.Set(newValue)
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
// 获取字段地址,用于调用方法
|
||||
fieldAddr := field.Addr()
|
||||
|
||||
// 检查字段是否实现了 ConfigItem 接口
|
||||
if fieldAddr.Type().Implements(reflect.TypeOf((*ConfigItem)(nil)).Elem()) {
|
||||
// 实现了接口,调用 ApplyDefaults 方法
|
||||
method := fieldAddr.MethodByName("ApplyDefaults")
|
||||
if method.IsValid() {
|
||||
results := method.Call(nil)
|
||||
if len(results) > 0 {
|
||||
if err, ok := results[0].Interface().(error); ok && err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if field.Kind() == reflect.Struct {
|
||||
// 为嵌套结构体确定其路径组件
|
||||
structPath := getFieldName(fieldType)
|
||||
|
||||
var currentPath string
|
||||
if parentPath != "" {
|
||||
currentPath = parentPath + "." + structPath
|
||||
} else {
|
||||
currentPath = structPath
|
||||
}
|
||||
|
||||
if err := ApplyConfigDefaults(fieldAddr.Interface(), currentPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 处理普通字段的默认值设置,传递父路径
|
||||
if err := applyConfigFieldDefaults(field, fieldType, parentPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyConfigFieldDefaults 处理单个字段的默认值设置和选项验证等
|
||||
func applyConfigFieldDefaults(field reflect.Value, fieldType reflect.StructField, parentPath string) error {
|
||||
// 构建当前字段的完整配置路径
|
||||
currentPath := getFieldName(fieldType)
|
||||
if parentPath != "" {
|
||||
currentPath = parentPath + "." + currentPath
|
||||
}
|
||||
|
||||
// 检查是否已设置值
|
||||
if structx.IsZeroValue(field) {
|
||||
defaultTag := fieldType.Tag.Get("default")
|
||||
if defaultTag == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := setFieldValue(field, defaultTag); err != nil {
|
||||
return fmt.Errorf("设置字段 [%s] 的默认值失败: %v", currentPath, err)
|
||||
}
|
||||
|
||||
LogDefaultValue(currentPath, defaultTag)
|
||||
} else {
|
||||
// 检查选项验证
|
||||
optionsTag := fieldType.Tag.Get("options")
|
||||
if optionsTag != "" {
|
||||
if err := validateOptions(currentPath, field, fieldType, optionsTag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setFieldValue 设置字段值
|
||||
func setFieldValue(field reflect.Value, defaultValue string) error {
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
field.SetString(defaultValue)
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(defaultValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetBool(val)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val, err := strconv.ParseInt(defaultValue, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetInt(val)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
val, err := strconv.ParseUint(defaultValue, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetUint(val)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val, err := strconv.ParseFloat(defaultValue, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetFloat(val)
|
||||
default:
|
||||
return fmt.Errorf("不支持的类型: %v", field.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFieldName 获取字段名称,用于日志输出
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
yamlTag := field.Tag.Get("yaml")
|
||||
if yamlTag != "" && yamlTag != "-" {
|
||||
return yamlTag
|
||||
}
|
||||
return strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// validateOptions 验证字段值是否在允许的选项范围内
|
||||
func validateOptions(configPath string, field reflect.Value, typeField reflect.StructField, optionsTag string) error {
|
||||
// 解析选项列表
|
||||
options := strings.Split(optionsTag, ",")
|
||||
if len(options) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清理选项空格
|
||||
for i, option := range options {
|
||||
options[i] = strings.TrimSpace(option)
|
||||
}
|
||||
|
||||
var fieldValue string
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
fieldValue = field.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fieldValue = strconv.FormatInt(field.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
fieldValue = strconv.FormatUint(field.Uint(), 10)
|
||||
case reflect.Bool:
|
||||
fieldValue = strconv.FormatBool(field.Bool())
|
||||
default:
|
||||
return fmt.Errorf("字段 %s 不支持选项验证,类型: %v", typeField.Name, field.Kind())
|
||||
}
|
||||
|
||||
// 检查字段值是否在允许的选项中
|
||||
for _, option := range options {
|
||||
if fieldValue == option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return NewConfigError(configPath, fmt.Sprintf("值 '%s' 不在允许的选项范围内: [%s]", fieldValue, strings.Join(options, ", ")))
|
||||
}
|
||||
85
server/pkg/starter/gorm.go
Normal file
85
server/pkg/starter/gorm.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mayfly-go/pkg/logx"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func initDB(dbConf DBConf) (*gorm.DB, error) {
|
||||
if dbConf.Dialect == DialectMySQL {
|
||||
return initMysql(dbConf)
|
||||
}
|
||||
|
||||
return initSqlite(dbConf)
|
||||
}
|
||||
|
||||
func initMysql(dbConf DBConf) (*gorm.DB, error) {
|
||||
logx.Infof("connecting to mysql [%s]", dbConf.Address)
|
||||
mysqlConfig := mysql.Config{
|
||||
DSN: dbConf.Username + ":" + dbConf.Password + "@tcp(" + dbConf.Address + ")/" + dbConf.Name + "?" + dbConf.Config, // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
|
||||
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
|
||||
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), getGormConfig()); err != nil {
|
||||
logx.Errorf("failed to connect to mysql! [%s]", err.Error())
|
||||
return nil, err
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(dbConf.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(dbConf.MaxOpenConns)
|
||||
|
||||
// 如果是开发环境时,打印sql语句
|
||||
if logx.GetConfig().IsDebug() {
|
||||
db = db.Debug()
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
}
|
||||
|
||||
func initSqlite(dbConf DBConf) (*gorm.DB, error) {
|
||||
logx.Infof("connecting to sqlite [%s]", dbConf.Address)
|
||||
if db, err := gorm.Open(sqlite.Open(dbConf.Address), getGormConfig()); err != nil {
|
||||
logx.Errorf("failed to connect to sqlite! [%s]", err.Error())
|
||||
return nil, err
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(dbConf.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(dbConf.MaxOpenConns)
|
||||
return db, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getGormConfig() *gorm.Config {
|
||||
sqlLogLevel := logger.Error
|
||||
logConf := logx.GetConfig()
|
||||
// 如果为配置文件中配置的系统日志级别为debug,则打印gorm执行的sql信息
|
||||
if logConf.IsDebug() {
|
||||
sqlLogLevel = logger.Info
|
||||
}
|
||||
|
||||
gormLogger := logger.New(
|
||||
log.New(logConf.GetLogOut(), "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // 慢 SQL 阈值
|
||||
LogLevel: sqlLogLevel, // 日志级别, 改为logger.Info即可显示sql语句
|
||||
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
|
||||
Colorful: false, // 禁用彩色打印
|
||||
},
|
||||
)
|
||||
|
||||
return &gorm.Config{NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
}, Logger: gormLogger}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package initialize
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
@@ -27,7 +26,7 @@ func AddInitFunc(initFunc InitFunc) {
|
||||
}
|
||||
|
||||
// 系统启动时,调用各个模块的初始化函数
|
||||
func InitOther() {
|
||||
func initOther() error {
|
||||
// 调用各个模块ioc组件注册初始化,优先调用ioc初始化注册函数和注入函数(可能在后续的InitFunc中需要用到依赖实例)
|
||||
for _, initIocFunc := range initIocFuncs {
|
||||
initIocFunc()
|
||||
@@ -35,11 +34,15 @@ func InitOther() {
|
||||
initIocFuncs = nil
|
||||
|
||||
// 为所有注册的实例注入其依赖的其他组件实例
|
||||
biz.ErrIsNil(ioc.InjectComponents())
|
||||
if err := ioc.InjectComponents(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 调用各个模块的初始化函数
|
||||
for _, initFunc := range initFuncs {
|
||||
go initFunc()
|
||||
}
|
||||
initFuncs = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
73
server/pkg/starter/option.go
Normal file
73
server/pkg/starter/option.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Option 是用于配置启动选项的函数类型
|
||||
type Option func(*Options)
|
||||
|
||||
// Options 包含所有启动时的配置选项
|
||||
type Options struct {
|
||||
// 日志保存器
|
||||
LogSaver func() req.SaveLogFunc
|
||||
|
||||
// 数据库初始化相关回调
|
||||
OnDbReady func(db *gorm.DB) error
|
||||
|
||||
// 路由注册完成回调
|
||||
OnRoutesReady func(engine *gin.Engine)
|
||||
|
||||
// 服务启动相关回调
|
||||
OnBeforeStart func()
|
||||
|
||||
// 静态资源路由配置
|
||||
StaticRouter *StaticRouter
|
||||
}
|
||||
|
||||
// WithLogSaver 设置日志保存器
|
||||
func WithLogSaver(saver func() req.SaveLogFunc) Option {
|
||||
return func(o *Options) {
|
||||
o.LogSaver = saver
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnDbReady 设置数据库准备就绪回调函数
|
||||
func WithOnDbReady(fn func(db *gorm.DB) error) Option {
|
||||
return func(o *Options) {
|
||||
o.OnDbReady = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnRoutesReady 设置路由准备就绪回调函数
|
||||
func WithOnRoutesReady(fn func(engine *gin.Engine)) Option {
|
||||
return func(o *Options) {
|
||||
o.OnRoutesReady = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnBeforeStart 设置服务启动前回调函数
|
||||
func WithOnBeforeStart(fn func()) Option {
|
||||
return func(o *Options) {
|
||||
o.OnBeforeStart = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithStaticRouter 添加静态资源路由
|
||||
func WithStaticRouter(staticRouter *StaticRouter) Option {
|
||||
return func(o *Options) {
|
||||
o.StaticRouter = staticRouter
|
||||
}
|
||||
}
|
||||
|
||||
// NewOptions 创建默认的选项配置
|
||||
func NewOptions(opts ...Option) *Options {
|
||||
options := &Options{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package initialize
|
||||
package starter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
"net/http"
|
||||
@@ -9,18 +10,13 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RouterApi
|
||||
// 该接口的实现类注册到ioc中,则会自动将请求配置注册到路由中
|
||||
type RouterApi interface {
|
||||
// ReqConfs 获取请求配置信息
|
||||
ReqConfs() *req.Confs
|
||||
// StaticRouter 静态资源路由配置
|
||||
type StaticRouter struct {
|
||||
Fs fs.FS // 静态资源文件系统
|
||||
Paths []string // 静态资源访问路径,如 /assets/*file
|
||||
}
|
||||
|
||||
type RouterConfig struct {
|
||||
ContextPath string // 请求路径上下文
|
||||
}
|
||||
|
||||
func InitRouter(router *gin.Engine, conf RouterConfig) *gin.Engine {
|
||||
func initRouter(router *gin.Engine, conf req.RouterConfig) *gin.Engine {
|
||||
// 没有路由即 404返回
|
||||
router.NoRoute(func(g *gin.Context) {
|
||||
g.JSON(http.StatusNotFound, gin.H{"code": 404, "msg": fmt.Sprintf("not found '%s:%s'", g.Request.Method, g.Request.URL.Path)})
|
||||
@@ -30,7 +26,7 @@ func InitRouter(router *gin.Engine, conf RouterConfig) *gin.Engine {
|
||||
api := router.Group(conf.ContextPath + "/api")
|
||||
|
||||
// 获取所有实现了RouterApi接口的实例,并注册对应路由
|
||||
ras := ioc.GetBeansByType[RouterApi]()
|
||||
ras := ioc.GetBeansByType[req.RouterApi]()
|
||||
for _, ra := range ras {
|
||||
confs := ra.ReqConfs()
|
||||
if group := confs.Group; group != "" {
|
||||
81
server/pkg/starter/starter.go
Normal file
81
server/pkg/starter/starter.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/validatorx"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Run(config Conf, opts ...Option) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// 应用配置默认值
|
||||
if err := config.ApplyDefaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := NewOptions(opts...)
|
||||
|
||||
// 初始化并赋值数据库全局变量
|
||||
db, err := initDB(config.DB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.Db = db
|
||||
if options.OnDbReady != nil {
|
||||
if err := options.OnDbReady(db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化缓存
|
||||
if err := initCache(config.Redis); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化其他需要启动时运行的方法
|
||||
if err := initOther(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 参数校验器初始化、如错误提示中文转译等
|
||||
validatorx.Init()
|
||||
|
||||
// jwt配置
|
||||
jwtConf := config.Jwt
|
||||
req.SetJwtConf(req.JwtConf{
|
||||
Key: jwtConf.Key,
|
||||
ExpireTime: jwtConf.ExpireTime,
|
||||
RefreshTokenExpireTime: jwtConf.RefreshTokenExpireTime,
|
||||
})
|
||||
|
||||
// 权限处理器
|
||||
req.UseBeforeHandlerInterceptor(req.PermissionHandler)
|
||||
// 日志处理器
|
||||
req.UseAfterHandlerInterceptor(req.LogHandler)
|
||||
|
||||
// 设置日志保存函数
|
||||
if options.LogSaver != nil {
|
||||
req.SetSaveLogFunc(options.LogSaver())
|
||||
}
|
||||
|
||||
// 启动前回调
|
||||
if options.OnBeforeStart != nil {
|
||||
options.OnBeforeStart()
|
||||
}
|
||||
|
||||
// 运行web服务
|
||||
return runWebServer(ctx, config.Server, options)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package initialize
|
||||
package starter
|
||||
|
||||
// 系统进程退出终止函数
|
||||
type TerminateFunc func()
|
||||
109
server/pkg/starter/web-server.go
Normal file
109
server/pkg/starter/web-server.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"mayfly-go/pkg/gox"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/middleware"
|
||||
"mayfly-go/pkg/req"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func runWebServer(ctx context.Context, serverConfig ServerConf, options *Options) error {
|
||||
// 设置gin日志输出器
|
||||
logOut := logx.GetConfig().GetLogOut()
|
||||
gin.DefaultErrorWriter = logOut
|
||||
gin.DefaultWriter = logOut
|
||||
|
||||
gin.SetMode(serverConfig.Model)
|
||||
|
||||
// i18n配置
|
||||
i18n.SetLang(serverConfig.Lang)
|
||||
|
||||
var router = gin.New()
|
||||
router.MaxMultipartMemory = 8 << 20
|
||||
// 初始化接口路由
|
||||
initRouter(router, req.RouterConfig{ContextPath: serverConfig.ContextPath})
|
||||
// 设置静态资源
|
||||
setStatic(router, serverConfig, options.StaticRouter)
|
||||
if options != nil && options.OnRoutesReady != nil {
|
||||
options.OnRoutesReady(router)
|
||||
}
|
||||
|
||||
// 是否允许跨域
|
||||
if serverConfig.Cors {
|
||||
router.Use(middleware.Cors())
|
||||
}
|
||||
|
||||
srv := http.Server{
|
||||
Addr: serverConfig.GetPort(),
|
||||
// 注册路由
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer gox.Recover()
|
||||
<-ctx.Done()
|
||||
logx.Info("Shutdown HTTP Server ...")
|
||||
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
err := srv.Shutdown(timeout)
|
||||
if err != nil {
|
||||
logx.Errorf("Failed to Shutdown HTTP Server: %v", err)
|
||||
}
|
||||
|
||||
Terminate()
|
||||
}()
|
||||
|
||||
logx.Infof("Listening and serving HTTP on %s", srv.Addr+serverConfig.ContextPath)
|
||||
var err error
|
||||
if serverConfig.TLS.Enable {
|
||||
err = srv.ListenAndServeTLS(serverConfig.TLS.CertFile, serverConfig.TLS.KeyFile)
|
||||
} else {
|
||||
err = srv.ListenAndServe()
|
||||
}
|
||||
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
logx.Info("HTTP Server Shutdown")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logx.Errorf("Failed to Start HTTP Server: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func setStatic(router *gin.Engine, serverConfig ServerConf, staticRouter *StaticRouter) {
|
||||
contextPath := serverConfig.ContextPath
|
||||
|
||||
if staticRouter != nil {
|
||||
fileServer := http.FileServer(http.FS(staticRouter.Fs))
|
||||
handler := WrapStaticHandler(http.StripPrefix(contextPath, fileServer))
|
||||
for _, p := range staticRouter.Paths {
|
||||
router.GET(contextPath+p, handler)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置静态资源
|
||||
for _, scs := range serverConfig.Statics {
|
||||
router.StaticFS(scs.RelativePath, http.Dir(scs.Root))
|
||||
}
|
||||
// 设置静态文件
|
||||
for _, sfs := range serverConfig.StaticFiles {
|
||||
router.StaticFile(sfs.RelativePath, sfs.Filepath)
|
||||
}
|
||||
}
|
||||
|
||||
func WrapStaticHandler(h http.Handler) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Cache-Control", `public, max-age=31536000`)
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
@@ -19,3 +19,24 @@ func NewInstance[T any]() T {
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
|
||||
// IsZeroValue 检查字段是否为零值
|
||||
func IsZeroValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
return v.String() == ""
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return v.Uint() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
|
||||
return v.IsNil()
|
||||
default:
|
||||
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,25 @@
|
||||
package static
|
||||
|
||||
import "embed"
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"mayfly-go/pkg/starter"
|
||||
)
|
||||
|
||||
// 使用1.16特性编译阶段将静态资源文件打包进编译好的程序
|
||||
var (
|
||||
//go:embed static/**
|
||||
Static embed.FS
|
||||
)
|
||||
|
||||
func Router() *starter.StaticRouter {
|
||||
sys, _ := fs.Sub(Static, "static")
|
||||
return &starter.StaticRouter{
|
||||
Fs: sys,
|
||||
Paths: []string{"/",
|
||||
"/favicon.ico",
|
||||
"/config.js",
|
||||
"/assets/*file",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user