first commit
This commit is contained in:
commit
d9ece60bff
|
@ -0,0 +1,34 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# 顶层配置文件
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# 所有文件通用配置
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# Go 文件配置
|
||||||
|
[*.go]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
# Markdown 文件配置
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
# YAML 文件配置
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# JSON 文件配置
|
||||||
|
[*.json]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Shell 脚本配置
|
||||||
|
[*.sh]
|
||||||
|
end_of_line = lf
|
|
@ -0,0 +1,12 @@
|
||||||
|
PORT=3000
|
||||||
|
MYSQL_DSN="root:200967tao@tcp(www.suyun.store:3306)/ego?charset=utf8mb4&parseTime=True&loc=Local"
|
||||||
|
REDIS_ADDR="www.suyun.store:6379"
|
||||||
|
REDIS_PW="tao200967"
|
||||||
|
REDIS_DB="0"
|
||||||
|
REDIS_POOL_SIZE=20
|
||||||
|
REDIS_READ_TIMEOUT=3s
|
||||||
|
JWT_SECRET="YouOnlyLiveOnce"
|
||||||
|
GIN_MODE="test"
|
||||||
|
LOG_LEVEL="info"
|
||||||
|
LOG_PATH="./logs/"
|
||||||
|
CORS_ALLOWED_ORIGINS="http://www.unbug.cn:3000,http://127.0.0.1:3000"
|
|
@ -0,0 +1,12 @@
|
||||||
|
PORT=3000
|
||||||
|
MYSQL_DSN="root:200967tao@tcp(192.168.1.12:3306)/ego?charset=utf8mb4&parseTime=True&loc=Local"
|
||||||
|
REDIS_ADDR="192.168.1.12:6379"
|
||||||
|
REDIS_PW="200967tao"
|
||||||
|
REDIS_DB="0"
|
||||||
|
REDIS_POOL_SIZE=20
|
||||||
|
REDIS_READ_TIMEOUT=3s
|
||||||
|
JWT_SECRET="YouOnlyLiveOnce"
|
||||||
|
GIN_MODE="test"
|
||||||
|
LOG_LEVEL="info"
|
||||||
|
LOG_PATH="./logs/"
|
||||||
|
CORS_ALLOWED_ORIGINS="http://localhost:3000,http://127.0.0.1:8080"
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||||
|
<file url="PROJECT" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,207 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CodeInsightWorkspaceSettings">
|
||||||
|
<option name="optimizeImportsOnTheFly" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="MavenImportPreferences">
|
||||||
|
<option name="generalSettings">
|
||||||
|
<MavenGeneralSettings>
|
||||||
|
<option name="mavenHome" value="D:/SoftWare/apache-maven-3.5.0/apache-maven-3.5.0" />
|
||||||
|
</MavenGeneralSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectInspectionProfilesVisibleTreeState">
|
||||||
|
<entry key="Project Default">
|
||||||
|
<profile-state>
|
||||||
|
<expanded-state>
|
||||||
|
<State />
|
||||||
|
<State>
|
||||||
|
<id>Android</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Bitwise operation issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Bitwise operation issuesJavaScript and TypeScript</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Class structureJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Code maturityJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Code style issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>CodeSpring CoreSpring</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Compiler issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Control flow issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>CorrectnessLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Cucumber</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>FinalizationJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>GeneralJavaScript and TypeScript</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Groovy</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>HTTP Client</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>IconsUsabilityLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>ImportsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Inheritance issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>InternationalizationLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>InteroperabilityLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>JUnitJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>JVM languages</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java 14Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java 15Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java 5Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java 7Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java 8Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java 9Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Java language level migration aidsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>JavaScript and TypeScript</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>JavadocJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Kotlin</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>LintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>MigrationKotlin</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Numeric issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Other problemsKotlin</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>PerformanceJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>PerformanceLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Potentially confusing code constructsGroovy</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Potentially confusing code constructsJavaScript and TypeScript</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Probable bugsJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Probable bugsKotlin</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Reactive Streams</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>ReactorReactive Streams</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Redundant constructsKotlin</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Resource managementJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>SQL</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>SecurityLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Spring</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Spring AOPSpring</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Spring BootSpring</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Spring CoreSpring</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Style issuesKotlin</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Test frameworksJVM languages</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>TestNGJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>Threading issuesJava</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>UI form</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>UsabilityLintAndroid</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>XMLSpring CoreSpring</id>
|
||||||
|
</State>
|
||||||
|
</expanded-state>
|
||||||
|
<selected-state>
|
||||||
|
<State>
|
||||||
|
<id>Android</id>
|
||||||
|
</State>
|
||||||
|
</selected-state>
|
||||||
|
</profile-state>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/DeployHelper.iml" filepath="$PROJECT_DIR$/.idea/DeployHelper.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="ALL" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="48503f58-7b9d-42e5-b962-e07f79b5cec9" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="GOROOT" url="file://$USER_HOME$/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.5.windows-amd64" />
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 5
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="30g0BlhEax2aR8AzLfMyUWJcy8p" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"Go Build.go build ego/cmd/ego.executor": "Run",
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.go.formatter.settings.were.checked": "true",
|
||||||
|
"RunOnceActivity.go.migrated.go.modules.settings": "true",
|
||||||
|
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"go.import.settings.migrated": "true",
|
||||||
|
"go.sdk.automatically.set": "true",
|
||||||
|
"last_opened_file_path": "E:/home/DeployHelper",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"settings.editor.selected.configurable": "shared-indexes",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="go build ego/cmd/ego" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||||
|
<module name="DeployHelper" />
|
||||||
|
<working_directory value="$PROJECT_DIR$" />
|
||||||
|
<kind value="PACKAGE" />
|
||||||
|
<package value="ego/cmd/ego" />
|
||||||
|
<directory value="$PROJECT_DIR$" />
|
||||||
|
<filePath value="$PROJECT_DIR$/cmd/ego/main.go" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Go Build.go build ego/cmd/ego" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-gosdk-3b128438d3f6-07d2d2d66b1e-org.jetbrains.plugins.go.sharedIndexes.bundled-GO-251.27812.54" />
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-GO-251.27812.54" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="48503f58-7b9d-42e5-b962-e07f79b5cec9" name="Changes" comment="" />
|
||||||
|
<created>1754033774462</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1754033774462</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="VgoProject">
|
||||||
|
<settings-migrated>true</settings-migrated>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,183 @@
|
||||||
|
.PHONY: build run test clean docker-build docker-run docker-compose-up docker-compose-down wire docs fmt lint deps dev-setup build-all install-tools check-env mod-tidy
|
||||||
|
|
||||||
|
# 构建变量
|
||||||
|
BINARY_NAME=ego
|
||||||
|
MAIN_FILE=cmd/ego/main.go
|
||||||
|
DOCKER_IMAGE=ego:latest
|
||||||
|
GOOS ?= $(shell go env GOOS)
|
||||||
|
GOARCH ?= $(shell go env GOARCH)
|
||||||
|
|
||||||
|
# Go版本检查
|
||||||
|
GO_VERSION := $(shell go version | cut -d ' ' -f 3 | sed 's/go//')
|
||||||
|
REQUIRED_GO_VERSION := 1.21
|
||||||
|
|
||||||
|
# 默认目标
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
# 帮助信息
|
||||||
|
help: ## 显示帮助信息
|
||||||
|
@echo "可用的命令:"
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
# 环境检查
|
||||||
|
check-env: ## 检查开发环境
|
||||||
|
@echo "检查Go版本..."
|
||||||
|
@if [ "$(shell printf '%s\n' $(REQUIRED_GO_VERSION) $(GO_VERSION) | sort -V | head -n1)" != "$(REQUIRED_GO_VERSION)" ]; then \
|
||||||
|
echo "错误: 需要Go $(REQUIRED_GO_VERSION)或更高版本,当前版本: $(GO_VERSION)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "✓ Go版本检查通过: $(GO_VERSION)"
|
||||||
|
@echo "检查必要工具..."
|
||||||
|
@command -v git >/dev/null 2>&1 || { echo "错误: 需要安装git"; exit 1; }
|
||||||
|
@echo "✓ 环境检查完成"
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
build: check-env ## 构建应用程序
|
||||||
|
@echo "构建 $(BINARY_NAME)..."
|
||||||
|
@go build -ldflags="-w -s" -o $(BINARY_NAME) ./$(MAIN_FILE)
|
||||||
|
@echo "✓ 构建完成: $(BINARY_NAME)"
|
||||||
|
|
||||||
|
# 运行
|
||||||
|
run: check-env ## 运行应用程序
|
||||||
|
@echo "运行应用程序..."
|
||||||
|
@go run ./$(MAIN_FILE)
|
||||||
|
|
||||||
|
# 开发模式运行
|
||||||
|
dev: check-env ## 开发模式运行(调试模式)
|
||||||
|
@echo "以开发模式运行..."
|
||||||
|
@GIN_MODE=debug LOG_LEVEL=debug go run ./$(MAIN_FILE)
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
test: ## 运行测试
|
||||||
|
@echo "运行测试..."
|
||||||
|
@go test -v -race -coverprofile=coverage.out ./...
|
||||||
|
@go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@echo "✓ 测试完成,覆盖率报告: coverage.html"
|
||||||
|
|
||||||
|
# 基准测试
|
||||||
|
bench: ## 运行基准测试
|
||||||
|
@echo "运行基准测试..."
|
||||||
|
@go test -bench=. -benchmem ./...
|
||||||
|
|
||||||
|
# 清理
|
||||||
|
clean: ## 清理构建文件
|
||||||
|
@echo "清理构建文件..."
|
||||||
|
@go clean
|
||||||
|
@rm -f $(BINARY_NAME)
|
||||||
|
@rm -f cmd/ego/$(BINARY_NAME)
|
||||||
|
@rm -f coverage.out coverage.html
|
||||||
|
@rm -rf dist/
|
||||||
|
@echo "✓ 清理完成"
|
||||||
|
|
||||||
|
# 整理依赖
|
||||||
|
mod-tidy: ## 整理Go模块依赖
|
||||||
|
@echo "整理依赖..."
|
||||||
|
@go mod tidy
|
||||||
|
@go mod verify
|
||||||
|
@echo "✓ 依赖整理完成"
|
||||||
|
|
||||||
|
# Docker 构建
|
||||||
|
docker-build: ## 构建Docker镜像
|
||||||
|
@echo "构建Docker镜像..."
|
||||||
|
@docker build -f deployments/Dockerfile -t $(DOCKER_IMAGE) .
|
||||||
|
@echo "✓ Docker镜像构建完成: $(DOCKER_IMAGE)"
|
||||||
|
|
||||||
|
# Docker 运行
|
||||||
|
docker-run: ## 运行Docker容器
|
||||||
|
@echo "运行Docker容器..."
|
||||||
|
@docker run -p 3000:3000 --env-file .env $(DOCKER_IMAGE)
|
||||||
|
|
||||||
|
# Docker Compose 启动
|
||||||
|
docker-compose-up: ## 使用Docker Compose启动服务
|
||||||
|
@echo "启动Docker Compose服务..."
|
||||||
|
@docker-compose -f deployments/docker-compose.yml up -d
|
||||||
|
@echo "✓ 服务已启动"
|
||||||
|
|
||||||
|
# Docker Compose 停止
|
||||||
|
docker-compose-down: ## 停止Docker Compose服务
|
||||||
|
@echo "停止Docker Compose服务..."
|
||||||
|
@docker-compose -f deployments/docker-compose.yml down
|
||||||
|
@echo "✓ 服务已停止"
|
||||||
|
|
||||||
|
# 生成 wire 依赖注入代码
|
||||||
|
wire: ## 生成依赖注入代码
|
||||||
|
@echo "生成Wire依赖注入代码..."
|
||||||
|
@cd internal/wire && wire
|
||||||
|
@echo "✓ Wire代码生成完成"
|
||||||
|
|
||||||
|
# 生成 API 文档
|
||||||
|
docs: ## 生成API文档
|
||||||
|
@echo "生成API文档..."
|
||||||
|
@swag init -g $(MAIN_FILE) -o ./api --parseDependency --parseInternal
|
||||||
|
@echo "✓ API文档生成完成"
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
deps: check-env ## 下载依赖包
|
||||||
|
@echo "下载依赖包..."
|
||||||
|
@go mod download
|
||||||
|
@go mod verify
|
||||||
|
@echo "✓ 依赖下载完成"
|
||||||
|
|
||||||
|
# 代码格式化
|
||||||
|
fmt: ## 格式化代码
|
||||||
|
@echo "格式化代码..."
|
||||||
|
@go fmt ./...
|
||||||
|
@gofmt -w .
|
||||||
|
@echo "✓ 代码格式化完成"
|
||||||
|
|
||||||
|
# 代码检查
|
||||||
|
lint: ## 代码静态检查
|
||||||
|
@echo "运行代码检查..."
|
||||||
|
@golangci-lint run --timeout=5m
|
||||||
|
@echo "✓ 代码检查完成"
|
||||||
|
|
||||||
|
# 安全检查
|
||||||
|
security: ## 运行安全检查
|
||||||
|
@echo "运行安全检查..."
|
||||||
|
@govulncheck ./...
|
||||||
|
@echo "✓ 安全检查完成"
|
||||||
|
|
||||||
|
# 开发环境设置
|
||||||
|
dev-setup: check-env ## 设置开发环境
|
||||||
|
@echo "设置开发环境..."
|
||||||
|
@if [ ! -f "configs/config.yaml" ]; then \
|
||||||
|
cp "configs/config.yaml.example" "configs/config.yaml"; \
|
||||||
|
echo "✓ 配置文件已创建: configs/config.yaml"; \
|
||||||
|
fi
|
||||||
|
@if [ ! -f ".env" ]; then \
|
||||||
|
cp ".env.example" ".env" 2>/dev/null || echo "# 请根据需要设置环境变量" > .env; \
|
||||||
|
echo "✓ 环境变量文件已创建: .env"; \
|
||||||
|
fi
|
||||||
|
@mkdir -p logs
|
||||||
|
@echo "✓ 日志目录已创建: logs/"
|
||||||
|
@$(MAKE) deps
|
||||||
|
@$(MAKE) install-tools
|
||||||
|
@$(MAKE) wire
|
||||||
|
@echo "✓ 开发环境设置完成!"
|
||||||
|
|
||||||
|
# 构建所有平台
|
||||||
|
build-all: check-env ## 构建所有平台的二进制文件
|
||||||
|
@echo "构建所有平台..."
|
||||||
|
@mkdir -p dist
|
||||||
|
@GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o dist/$(BINARY_NAME)-linux-amd64 ./$(MAIN_FILE)
|
||||||
|
@GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o dist/$(BINARY_NAME)-windows-amd64.exe ./$(MAIN_FILE)
|
||||||
|
@GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o dist/$(BINARY_NAME)-darwin-amd64 ./$(MAIN_FILE)
|
||||||
|
@GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s" -o dist/$(BINARY_NAME)-darwin-arm64 ./$(MAIN_FILE)
|
||||||
|
@echo "✓ 所有平台构建完成,文件位于 dist/ 目录"
|
||||||
|
|
||||||
|
# 安装开发工具
|
||||||
|
install-tools: ## 安装开发工具
|
||||||
|
@echo "安装开发工具..."
|
||||||
|
@go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
@go install github.com/google/wire/cmd/wire@latest
|
||||||
|
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
@go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
@echo "✓ 开发工具安装完成"
|
||||||
|
|
||||||
|
# 运行所有检查
|
||||||
|
check-all: fmt lint test security ## 运行所有代码质量检查
|
||||||
|
@echo "✓ 所有检查完成"
|
||||||
|
|
||||||
|
# 发布准备
|
||||||
|
release-prep: check-all build-all docs ## 准备发布版本
|
||||||
|
@echo "✓ 发布准备完成"
|
Binary file not shown.
After Width: | Height: | Size: 596 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.8 MiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,128 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"ego/internal/conf"
|
||||||
|
"ego/internal/router"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @title EGO API
|
||||||
|
// @version 1.0
|
||||||
|
// @description EGO 系统 API 文档
|
||||||
|
// @termsOfService http://swagger.io/terms/
|
||||||
|
// @contact.name API Support
|
||||||
|
// @contact.url http://www.swagger.io/support
|
||||||
|
// @contact.email support@swagger.io
|
||||||
|
// @license.name Apache 2.0
|
||||||
|
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
// @host 127.0.0.1:3000
|
||||||
|
// @BasePath /
|
||||||
|
// @securityDefinitions.apikey BearerAuth
|
||||||
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 从配置文件读取配置
|
||||||
|
if err := conf.Init(); err != nil {
|
||||||
|
logger.Error(nil, "配置初始化失败", zap.Error(err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 gin 模式
|
||||||
|
ginMode := getGinMode()
|
||||||
|
gin.SetMode(ginMode)
|
||||||
|
logger.Info(nil, "Gin模式设置完成", zap.String("mode", ginMode))
|
||||||
|
|
||||||
|
// 创建路由
|
||||||
|
engine := router.NewRouter()
|
||||||
|
|
||||||
|
// 获取服务端口
|
||||||
|
port := getServerPort()
|
||||||
|
|
||||||
|
// 创建HTTP服务器
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: port,
|
||||||
|
Handler: engine,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
IdleTimeout: 60 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在goroutine中启动服务器
|
||||||
|
go func() {
|
||||||
|
logger.Info(nil, "服务器启动中...", zap.String("addr", port))
|
||||||
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
logger.Error(nil, "服务器启动失败", zap.Error(err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.Info(nil, "服务器启动成功", zap.String("addr", port))
|
||||||
|
|
||||||
|
// 等待中断信号来优雅地关闭服务器
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
|
||||||
|
logger.Info(nil, "收到关闭信号,开始优雅关闭服务器...")
|
||||||
|
|
||||||
|
// 创建一个5秒超时的context
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// 优雅关闭HTTP服务器
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
logger.Error(nil, "服务器强制关闭", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logger.Info(nil, "HTTP服务器已优雅关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭其他资源
|
||||||
|
conf.Close()
|
||||||
|
|
||||||
|
logger.Info(nil, "服务器关闭完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGinMode 获取Gin运行模式
|
||||||
|
func getGinMode() string {
|
||||||
|
mode := os.Getenv("GIN_MODE")
|
||||||
|
if mode == "" {
|
||||||
|
mode = gin.ReleaseMode // 默认为生产模式
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证模式是否有效
|
||||||
|
switch mode {
|
||||||
|
case gin.DebugMode, gin.ReleaseMode, gin.TestMode:
|
||||||
|
return mode
|
||||||
|
default:
|
||||||
|
logger.Warn(nil, "无效的GIN_MODE,使用默认值",
|
||||||
|
zap.String("invalid_mode", mode),
|
||||||
|
zap.String("default_mode", gin.ReleaseMode))
|
||||||
|
return gin.ReleaseMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getServerPort 获取服务器端口
|
||||||
|
func getServerPort() string {
|
||||||
|
port := os.Getenv("SERVER_PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = ":3000" // 默认端口
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保端口格式正确
|
||||||
|
if port[0] != ':' {
|
||||||
|
port = ":" + port
|
||||||
|
}
|
||||||
|
|
||||||
|
return port
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
# EGO 应用配置示例文件
|
||||||
|
# 复制此文件为 config.yaml 并根据实际情况修改配置
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: ":3000"
|
||||||
|
mode: "release" # debug, release, test
|
||||||
|
|
||||||
|
database:
|
||||||
|
host: "localhost"
|
||||||
|
port: 3306
|
||||||
|
user: "your_username"
|
||||||
|
password: "your_password"
|
||||||
|
dbname: "ego_db"
|
||||||
|
charset: "utf8mb4"
|
||||||
|
parse_time: true
|
||||||
|
loc: "Local"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: "localhost"
|
||||||
|
port: 6379
|
||||||
|
password: ""
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: "your_jwt_secret_key"
|
||||||
|
expire_time: 24h
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: "info" # debug, info, warn, error
|
||||||
|
file_path: "logs/app.log"
|
||||||
|
max_size: 100 # MB
|
||||||
|
max_age: 30 # days
|
||||||
|
max_backups: 5
|
|
@ -0,0 +1,41 @@
|
||||||
|
# 构建阶段
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 安装必要的包
|
||||||
|
RUN apk add --no-cache git ca-certificates tzdata
|
||||||
|
|
||||||
|
# 复制go mod文件
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# 下载依赖
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# 复制源代码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 构建应用程序
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ego ./cmd/ego
|
||||||
|
|
||||||
|
# 运行阶段
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# 安装ca证书和时区数据
|
||||||
|
RUN apk --no-cache add ca-certificates tzdata
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 从构建阶段复制二进制文件
|
||||||
|
COPY --from=builder /app/ego .
|
||||||
|
|
||||||
|
# 创建必要的目录
|
||||||
|
RUN mkdir -p logs configs
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# 运行应用程序
|
||||||
|
CMD ["./ego"]
|
|
@ -0,0 +1,53 @@
|
||||||
|
services:
|
||||||
|
ego-app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: deployments/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- GIN_MODE=release
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- ../configs:/app/configs
|
||||||
|
- ../logs:/app/logs
|
||||||
|
networks:
|
||||||
|
- ego-network
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ego-mysql
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
|
MYSQL_DATABASE: ego_db
|
||||||
|
MYSQL_USER: ego_user
|
||||||
|
MYSQL_PASSWORD: ego_password
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ../sql:/docker-entrypoint-initdb.d
|
||||||
|
networks:
|
||||||
|
- ego-network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: ego-redis
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- ego-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ego-network:
|
||||||
|
driver: bridge
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,76 @@
|
||||||
|
module ego
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-contrib/cors v1.7.6
|
||||||
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/google/wire v0.6.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||||
|
github.com/redis/go-redis/v9 v9.11.0
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
github.com/swaggo/files v1.0.1
|
||||||
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
|
github.com/swaggo/swag v1.16.4
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
golang.org/x/crypto v0.40.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
gorm.io/driver/mysql v1.6.0
|
||||||
|
gorm.io/gorm v1.30.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/bytedance/sonic v1.13.3 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.23.1 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/lestrrat-go/strftime v1.1.0 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||||
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/arch v0.19.0 // indirect
|
||||||
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
golang.org/x/tools v0.35.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,265 @@
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||||
|
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
|
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||||
|
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||||
|
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||||
|
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||||
|
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||||
|
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||||
|
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||||
|
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||||
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||||
|
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||||
|
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
|
||||||
|
github.com/lestrrat-go/strftime v1.1.0 h1:gMESpZy44/4pXLO/m+sL0yBd1W6LjgjrrD4a68Gapyg=
|
||||||
|
github.com/lestrrat-go/strftime v1.1.0/go.mod h1:uzeIB52CeUJenCo1syghlugshMysrqUT51HlxphXVeI=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||||
|
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||||
|
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||||
|
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||||
|
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||||
|
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||||
|
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||||
|
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||||
|
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||||
|
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||||
|
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
@ -0,0 +1,194 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
RedisClient *redis.Client
|
||||||
|
once sync.Once
|
||||||
|
initErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedisConfig Redis连接配置
|
||||||
|
type RedisConfig struct {
|
||||||
|
Addr string `env:"REDIS_ADDR"`
|
||||||
|
Password string `env:"REDIS_PW"`
|
||||||
|
DB int `env:"REDIS_DB"`
|
||||||
|
MaxRetries int `env:"REDIS_RETRIES"`
|
||||||
|
PoolSize int `env:"REDIS_POOL_SIZE"`
|
||||||
|
ReadTimeout time.Duration `env:"REDIS_READ_TIMEOUT"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis 初始化Redis连接
|
||||||
|
func Redis() error {
|
||||||
|
once.Do(func() {
|
||||||
|
initErr = initRedis()
|
||||||
|
})
|
||||||
|
return initErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// initRedis 实际的Redis初始化逻辑
|
||||||
|
func initRedis() error {
|
||||||
|
config, err := loadRedisConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(nil, "Redis配置加载失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("redis配置加载失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建客户端
|
||||||
|
client := redis.NewClient(&redis.Options{
|
||||||
|
Addr: config.Addr,
|
||||||
|
Password: config.Password,
|
||||||
|
DB: config.DB,
|
||||||
|
MaxRetries: config.MaxRetries,
|
||||||
|
PoolSize: config.PoolSize,
|
||||||
|
ReadTimeout: config.ReadTimeout,
|
||||||
|
WriteTimeout: config.ReadTimeout * 2,
|
||||||
|
// 添加更多配置选项
|
||||||
|
DialTimeout: 5 * time.Second,
|
||||||
|
PoolTimeout: 4 * time.Second,
|
||||||
|
MinIdleConns: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证连接
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := client.Ping(ctx).Err(); err != nil {
|
||||||
|
logger.Error(nil, "Redis连接失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("addr", config.Addr),
|
||||||
|
zap.Int("db", config.DB))
|
||||||
|
return fmt.Errorf("Redis连接失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisClient = client
|
||||||
|
logger.Info(nil, "Redis连接成功",
|
||||||
|
zap.String("address", config.Addr),
|
||||||
|
zap.Int("db", config.DB),
|
||||||
|
zap.Int("pool_size", config.PoolSize),
|
||||||
|
zap.Duration("read_timeout", config.ReadTimeout))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭Redis连接
|
||||||
|
func Close() error {
|
||||||
|
if RedisClient != nil {
|
||||||
|
err := RedisClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(nil, "关闭Redis失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("关闭Redis失败: %w", err)
|
||||||
|
}
|
||||||
|
logger.Info(nil, "Redis已关闭")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClient 获取Redis客户端(用于外部调用)
|
||||||
|
func GetClient() (*redis.Client, error) {
|
||||||
|
if RedisClient == nil {
|
||||||
|
return nil, fmt.Errorf("Redis连接未初始化")
|
||||||
|
}
|
||||||
|
return RedisClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConnected 检查Redis连接状态
|
||||||
|
func IsConnected() bool {
|
||||||
|
if RedisClient == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return RedisClient.Ping(ctx).Err() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRedisConfig 从环境变量加载配置
|
||||||
|
func loadRedisConfig() (*RedisConfig, error) {
|
||||||
|
// 必要参数校验
|
||||||
|
addr := getEnvWithDefault("REDIS_ADDR", "localhost:6379")
|
||||||
|
|
||||||
|
dbStr := getEnvWithDefault("REDIS_DB", "0")
|
||||||
|
db, err := strconv.Atoi(dbStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无效的 REDIS_DB 值 '%s': %w", dbStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础配置
|
||||||
|
config := &RedisConfig{
|
||||||
|
Addr: addr,
|
||||||
|
Password: os.Getenv("REDIS_PW"), // 密码可以为空
|
||||||
|
DB: db,
|
||||||
|
MaxRetries: 5,
|
||||||
|
PoolSize: 10,
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取扩展配置
|
||||||
|
if err := loadExtendedConfig(config); err != nil {
|
||||||
|
return nil, fmt.Errorf("加载扩展配置失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadExtendedConfig 加载扩展配置
|
||||||
|
func loadExtendedConfig(config *RedisConfig) error {
|
||||||
|
// 连接池大小
|
||||||
|
if val := os.Getenv("REDIS_POOL_SIZE"); val != "" {
|
||||||
|
poolSize, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无效的 REDIS_POOL_SIZE 值 '%s': %w", val, err)
|
||||||
|
}
|
||||||
|
if poolSize <= 0 {
|
||||||
|
return fmt.Errorf("REDIS_POOL_SIZE 必须大于 0")
|
||||||
|
}
|
||||||
|
config.PoolSize = poolSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重试次数
|
||||||
|
if val := os.Getenv("REDIS_RETRIES"); val != "" {
|
||||||
|
retries, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无效的 REDIS_RETRIES 值 '%s': %w", val, err)
|
||||||
|
}
|
||||||
|
if retries < 0 {
|
||||||
|
return fmt.Errorf("REDIS_RETRIES 不能小于 0")
|
||||||
|
}
|
||||||
|
config.MaxRetries = retries
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取超时
|
||||||
|
if val := os.Getenv("REDIS_READ_TIMEOUT"); val != "" {
|
||||||
|
timeout, err := time.ParseDuration(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无效的 REDIS_READ_TIMEOUT 值 '%s': %w", val, err)
|
||||||
|
}
|
||||||
|
if timeout <= 0 {
|
||||||
|
return fmt.Errorf("REDIS_READ_TIMEOUT 必须大于 0")
|
||||||
|
}
|
||||||
|
config.ReadTimeout = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnvWithDefault 获取环境变量,如果不存在则返回默认值
|
||||||
|
func getEnvWithDefault(key, defaultValue string) string {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
conf "ego/internal/conf/locales"
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ping 状态检查页面
|
||||||
|
// @Summary Get a message
|
||||||
|
// @Description Get a message from the server
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} map[string]string
|
||||||
|
// @Router /ping [get]
|
||||||
|
func Ping(c *gin.Context) {
|
||||||
|
logger.Info(c, "Ping", zap.Field{Key: "msg", Type: zapcore.StringType, String: "Ping"})
|
||||||
|
c.JSON(200, serializer.Succ("tong", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorResponse 返回错误消息
|
||||||
|
func ErrorResponse(err error) serializer.Response {
|
||||||
|
var ve validator.ValidationErrors
|
||||||
|
if errors.As(err, &ve) {
|
||||||
|
for _, e := range ve {
|
||||||
|
field := conf.T(fmt.Sprintf("Field.%s", e.Field()))
|
||||||
|
tag := conf.T(fmt.Sprintf("Tag.Valid.%s", e.Tag()))
|
||||||
|
return serializer.ParamErr(
|
||||||
|
fmt.Sprintf("%s%s", field, tag),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var unmarshalTypeError *json.UnmarshalTypeError
|
||||||
|
if errors.As(err, &unmarshalTypeError) {
|
||||||
|
return serializer.ParamErr("JSON类型不匹配", err)
|
||||||
|
}
|
||||||
|
return serializer.ParamErr("参数错误", err)
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/cache"
|
||||||
|
"ego/internal/conf/locales"
|
||||||
|
"ego/internal/util"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init 初始化所有配置
|
||||||
|
func Init() error {
|
||||||
|
// 1. 加载环境变量
|
||||||
|
if err := loadEnvironmentVariables(); err != nil {
|
||||||
|
// 这里不能使用logger,因为还没初始化
|
||||||
|
return fmt.Errorf("环境变量加载失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 初始化日志系统(在验证环境变量之前,因为后续步骤需要使用logger)
|
||||||
|
if err := logger.BuildLogger(); err != nil {
|
||||||
|
// 这里不能使用logger,因为初始化失败了
|
||||||
|
return fmt.Errorf("日志系统初始化失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证必要环境变量
|
||||||
|
if err := validateRequiredEnvVars(); err != nil {
|
||||||
|
logger.Error(nil, "环境变量验证失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("环境变量验证失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 加载翻译文件
|
||||||
|
if err := locales.LoadLocales(); err != nil {
|
||||||
|
logger.Error(nil, "翻译文件加载失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("翻译文件加载失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 连接数据库
|
||||||
|
mysqlDSN := os.Getenv("MYSQL_DSN")
|
||||||
|
if err := Database(mysqlDSN); err != nil {
|
||||||
|
logger.Error(nil, "数据库初始化失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("数据库初始化失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 连接 Redis
|
||||||
|
if err := cache.Redis(); err != nil {
|
||||||
|
logger.Error(nil, "Redis初始化失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("Redis初始化失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 输出JWT密钥状态日志(在logger初始化完成后)
|
||||||
|
util.LogJwtKeyStatus()
|
||||||
|
|
||||||
|
logger.Info(nil, "所有配置初始化完成")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭所有资源
|
||||||
|
func Close() {
|
||||||
|
logger.Info(nil, "开始关闭应用程序资源...")
|
||||||
|
|
||||||
|
// 关闭数据库
|
||||||
|
if err := closeDatabase(); err != nil {
|
||||||
|
logger.Error(nil, "数据库关闭失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logger.Info(nil, "数据库已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭 Redis
|
||||||
|
if err := cache.Close(); err != nil {
|
||||||
|
logger.Error(nil, "Redis关闭失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logger.Info(nil, "Redis已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(nil, "应用程序资源关闭完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadEnvironmentVariables 加载环境变量并处理错误
|
||||||
|
func loadEnvironmentVariables() error {
|
||||||
|
// 尝试加载.env文件,如果文件不存在也不报错
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
// 只有在文件存在但读取失败时才报错
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("加载 .env 文件失败: %w", err)
|
||||||
|
}
|
||||||
|
// .env文件不存在,使用系统环境变量
|
||||||
|
logger.Info(nil, ".env 文件不存在,使用系统环境变量")
|
||||||
|
} else {
|
||||||
|
logger.Info(nil, "成功加载 .env 文件")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRequiredEnvVars 验证必要的环境变量
|
||||||
|
func validateRequiredEnvVars() error {
|
||||||
|
requiredVars := map[string]string{
|
||||||
|
"MYSQL_DSN": "数据库连接字符串",
|
||||||
|
}
|
||||||
|
|
||||||
|
var missingVars []string
|
||||||
|
for envVar, description := range requiredVars {
|
||||||
|
if os.Getenv(envVar) == "" {
|
||||||
|
missingVars = append(missingVars, fmt.Sprintf("%s (%s)", envVar, description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingVars) > 0 {
|
||||||
|
return fmt.Errorf("缺少必要环境变量: %v", missingVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
glogger "gorm.io/gorm/logger"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Db 数据库链接单例
|
||||||
|
Db *gorm.DB
|
||||||
|
once sync.Once
|
||||||
|
dbInitErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDb 初始化数据库连接
|
||||||
|
func NewDb() *gorm.DB {
|
||||||
|
return Db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database 初始化mysql连接
|
||||||
|
func Database(connString string) error {
|
||||||
|
once.Do(func() {
|
||||||
|
dbInitErr = initDatabase(connString)
|
||||||
|
})
|
||||||
|
return dbInitErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDatabase 实际的数据库初始化逻辑
|
||||||
|
func initDatabase(connString string) error {
|
||||||
|
if connString == "" {
|
||||||
|
err := fmt.Errorf("数据库连接字符串不能为空")
|
||||||
|
logger.Error(nil, "数据库初始化失败", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化GORM日志配置
|
||||||
|
logLevel := getGormLogLevel()
|
||||||
|
newLogger := glogger.New(
|
||||||
|
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||||
|
glogger.Config{
|
||||||
|
SlowThreshold: time.Second,
|
||||||
|
LogLevel: logLevel,
|
||||||
|
IgnoreRecordNotFoundError: true,
|
||||||
|
Colorful: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 配置 gorm
|
||||||
|
db, err := gorm.Open(mysql.Open(connString), &gorm.Config{
|
||||||
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
|
Logger: newLogger,
|
||||||
|
NamingStrategy: schema.NamingStrategy{TablePrefix: ""},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(nil, "数据库连接失败", zap.Error(err), zap.String("connString", maskConnectionString(connString)))
|
||||||
|
return fmt.Errorf("数据库连接失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取底层的sql.DB对象
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(nil, "获取数据库实例失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("获取数据库实例失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置连接池
|
||||||
|
if err := configureConnectionPool(sqlDB); err != nil {
|
||||||
|
logger.Error(nil, "配置数据库连接池失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("配置数据库连接池失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
if err := sqlDB.Ping(); err != nil {
|
||||||
|
logger.Error(nil, "数据库连接测试失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("数据库连接测试失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Db = db
|
||||||
|
logger.Info(nil, "数据库连接成功",
|
||||||
|
zap.String("connString", maskConnectionString(connString)),
|
||||||
|
zap.Int("maxOpenConns", 20),
|
||||||
|
zap.Int("maxIdleConns", 10))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureConnectionPool 配置数据库连接池
|
||||||
|
func configureConnectionPool(sqlDB *sql.DB) error {
|
||||||
|
// 设置空闲连接池中连接的最大数量
|
||||||
|
sqlDB.SetMaxIdleConns(10)
|
||||||
|
// 设置打开数据库连接的最大数量
|
||||||
|
sqlDB.SetMaxOpenConns(20)
|
||||||
|
// 设置连接可复用的最大时间
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
// 设置连接空闲的最大时间
|
||||||
|
sqlDB.SetConnMaxIdleTime(time.Minute * 30)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGormLogLevel 根据环境变量获取GORM日志级别
|
||||||
|
func getGormLogLevel() glogger.LogLevel {
|
||||||
|
switch os.Getenv("GORM_LOG_LEVEL") {
|
||||||
|
case "silent":
|
||||||
|
return glogger.Silent
|
||||||
|
case "error":
|
||||||
|
return glogger.Error
|
||||||
|
case "warn":
|
||||||
|
return glogger.Warn
|
||||||
|
case "info":
|
||||||
|
return glogger.Info
|
||||||
|
default:
|
||||||
|
// 根据应用环境决定默认级别
|
||||||
|
if os.Getenv("GIN_MODE") == "release" {
|
||||||
|
return glogger.Error
|
||||||
|
}
|
||||||
|
return glogger.Info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maskConnectionString 遮盖连接字符串中的敏感信息
|
||||||
|
func maskConnectionString(connString string) string {
|
||||||
|
// 简单遮盖密码部分,实际项目中可以使用更复杂的正则表达式
|
||||||
|
return "***已遮盖敏感信息***"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration 执行数据迁移
|
||||||
|
func Migration() error {
|
||||||
|
if Db == nil {
|
||||||
|
return fmt.Errorf("数据库连接未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动迁移模式
|
||||||
|
models := []any{}
|
||||||
|
|
||||||
|
for _, model := range models {
|
||||||
|
if err := Db.AutoMigrate(model); err != nil {
|
||||||
|
logger.Error(nil, "数据库迁移失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("model", fmt.Sprintf("%T", model)))
|
||||||
|
return fmt.Errorf("迁移模型 %T 失败: %w", model, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(nil, "数据库迁移完成", zap.Int("models", len(models)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDB 获取数据库连接(用于外部调用)
|
||||||
|
func GetDB() (*gorm.DB, error) {
|
||||||
|
if Db == nil {
|
||||||
|
return nil, fmt.Errorf("数据库连接未初始化")
|
||||||
|
}
|
||||||
|
return Db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeDatabase 安全关闭数据库连接
|
||||||
|
func closeDatabase() error {
|
||||||
|
if Db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlDB, err := Db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取数据库连接失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlDB.Close()
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package locales
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed zh-cn.yaml
|
||||||
|
var Zhcn string
|
||||||
|
|
||||||
|
// Dictionary 字典
|
||||||
|
var Dictionary *map[any]any
|
||||||
|
|
||||||
|
// LoadLocales 读取国际化文件
|
||||||
|
func LoadLocales() error {
|
||||||
|
m := make(map[any]any)
|
||||||
|
err := yaml.Unmarshal([]byte(Zhcn), &m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary = &m
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// T 翻译
|
||||||
|
func T(key string) string {
|
||||||
|
dic := *Dictionary
|
||||||
|
keys := strings.Split(key, ".")
|
||||||
|
for index, path := range keys {
|
||||||
|
// 如果到达了最后一层,寻找目标翻译
|
||||||
|
if len(keys) == (index + 1) {
|
||||||
|
for k, v := range dic {
|
||||||
|
if k, ok := k.(string); ok {
|
||||||
|
if k == path {
|
||||||
|
if value, ok := v.(string); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
// 如果还有下一层,继续寻找
|
||||||
|
for k, v := range dic {
|
||||||
|
if ks, ok := k.(string); !ok {
|
||||||
|
return ""
|
||||||
|
} else if ks == path {
|
||||||
|
if dic, ok = v.(map[any]any); !ok {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
Tag:
|
||||||
|
required: "必须存在,而且不能为空"
|
||||||
|
min: "不够长"
|
||||||
|
max: "太长"
|
||||||
|
Field:
|
||||||
|
Name: "名称"
|
||||||
|
Nickname: "用户昵称"
|
||||||
|
UserName: "用户名"
|
||||||
|
PassWord: "密码"
|
||||||
|
PassWordConfirm: "密码校验"
|
||||||
|
Checked: "已选中"
|
|
@ -0,0 +1,17 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSetup 是测试设置函数
|
||||||
|
func TestSetup(t *testing.T) {
|
||||||
|
// 这里可以添加测试前的设置代码
|
||||||
|
t.Log("测试环境设置")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTeardown 是测试清理函数
|
||||||
|
func TestTeardown(t *testing.T) {
|
||||||
|
// 这里可以添加测试后的清理代码
|
||||||
|
t.Log("测试环境清理")
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type BaseHandler interface {
|
||||||
|
// Create 创建
|
||||||
|
Create(c *gin.Context)
|
||||||
|
// DeleteByID 删除
|
||||||
|
DeleteByID(c *gin.Context)
|
||||||
|
// UpdateByID 更新
|
||||||
|
UpdateByID(c *gin.Context)
|
||||||
|
// GetByID 获取单个
|
||||||
|
GetByID(c *gin.Context)
|
||||||
|
// DeleteByIDs 批量删除
|
||||||
|
DeleteByIDs(c *gin.Context)
|
||||||
|
// GetByCondition 条件查询
|
||||||
|
GetByCondition(c *gin.Context)
|
||||||
|
// ListByIDs 批量查询
|
||||||
|
ListByIDs(c *gin.Context)
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/service"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysDeployFileHandler 部署文件处理器
|
||||||
|
type SysDeployFileHandler struct {
|
||||||
|
deployFileService *service.SysDeployFileService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysDeployFileHandler 构建部署文件处理器
|
||||||
|
func NewSysDeployFileHandler(deployFileService *service.SysDeployFileService) *SysDeployFileHandler {
|
||||||
|
return &SysDeployFileHandler{
|
||||||
|
deployFileService: deployFileService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建部署文件记录
|
||||||
|
// @Summary 创建部署文件记录
|
||||||
|
// @Description 创建新的部署文件记录
|
||||||
|
// @Tags 部署文件管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param deployFile body model.SysDeployFile true "部署文件信息"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /deploy-files [post]
|
||||||
|
func (h *SysDeployFileHandler) Create(c *gin.Context) {
|
||||||
|
logger.Info(c, "创建部署文件记录")
|
||||||
|
c.JSON(200, h.deployFileService.Create(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID获取部署文件记录
|
||||||
|
// @Summary 获取部署文件记录
|
||||||
|
// @Description 根据ID获取部署文件记录详情
|
||||||
|
// @Tags 部署文件管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "部署ID"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /deploy-files/{id} [get]
|
||||||
|
func (h *SysDeployFileHandler) GetByID(c *gin.Context) {
|
||||||
|
logger.Info(c, "获取部署文件记录", zap.String("id", c.Param("id")))
|
||||||
|
c.JSON(200, h.deployFileService.GetByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateByID 根据ID更新部署文件记录
|
||||||
|
// @Summary 更新部署文件记录
|
||||||
|
// @Description 根据ID更新部署文件记录
|
||||||
|
// @Tags 部署文件管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "部署ID"
|
||||||
|
// @Param deployFile body model.SysDeployFile true "部署文件信息"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /deploy-files/{id} [put]
|
||||||
|
func (h *SysDeployFileHandler) UpdateByID(c *gin.Context) {
|
||||||
|
logger.Info(c, "更新部署文件记录", zap.String("id", c.Param("id")))
|
||||||
|
c.JSON(200, h.deployFileService.UpdateByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID 根据ID删除部署文件记录
|
||||||
|
// @Summary 删除部署文件记录
|
||||||
|
// @Description 根据ID删除部署文件记录
|
||||||
|
// @Tags 部署文件管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "部署ID"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /deploy-files/{id} [delete]
|
||||||
|
func (h *SysDeployFileHandler) DeleteByID(c *gin.Context) {
|
||||||
|
logger.Info(c, "删除部署文件记录", zap.String("id", c.Param("id")))
|
||||||
|
c.JSON(200, h.deployFileService.DeleteByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCondition 条件查询部署文件记录
|
||||||
|
// @Summary 条件查询部署文件记录
|
||||||
|
// @Description 根据条件分页查询部署文件记录
|
||||||
|
// @Tags 部署文件管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param params query types.Params true "查询参数"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /deploy-files [get]
|
||||||
|
func (h *SysDeployFileHandler) GetByCondition(c *gin.Context) {
|
||||||
|
logger.Info(c, "条件查询部署文件记录")
|
||||||
|
c.JSON(200, h.deployFileService.GetByCondition(c))
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/service"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysLoginLogHandler 登录日志处理器
|
||||||
|
type SysLoginLogHandler struct {
|
||||||
|
sysLoginLogService *service.SysLoginLogService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysLoginLogHandler 构建登录日志处理器
|
||||||
|
func NewSysLoginLogHandler(sysLoginLogService *service.SysLoginLogService) *SysLoginLogHandler {
|
||||||
|
return &SysLoginLogHandler{
|
||||||
|
sysLoginLogService: sysLoginLogService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建登录日志
|
||||||
|
func (h *SysLoginLogHandler) Create(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.Create(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID 根据ID删除登录日志
|
||||||
|
func (h *SysLoginLogHandler) DeleteByID(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.DeleteByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateByID 根据ID更新登录日志
|
||||||
|
func (h *SysLoginLogHandler) UpdateByID(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.UpdateByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID获取登录日志
|
||||||
|
func (h *SysLoginLogHandler) GetByID(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.GetByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCondition 根据条件获取登录日志
|
||||||
|
func (h *SysLoginLogHandler) GetByCondition(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.GetByCondition(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByIDs 根据ID列表获取登录日志
|
||||||
|
func (h *SysLoginLogHandler) ListByIDs(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.ListByIDs(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByIDs 根据ID列表删除登录日志
|
||||||
|
func (h *SysLoginLogHandler) DeleteByIDs(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.sysLoginLogService.DeleteByIDs(c))
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/service"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysUploadHandler 文件上传处理器
|
||||||
|
type SysUploadHandler struct {
|
||||||
|
UploadSvc *service.SysUploadService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysUploadHandler 构建文件上传处理器
|
||||||
|
func NewSysUploadHandler(uploadSvc *service.SysUploadService) *SysUploadHandler {
|
||||||
|
return &SysUploadHandler{
|
||||||
|
UploadSvc: uploadSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadZip 上传压缩包
|
||||||
|
// @Summary 上传压缩包
|
||||||
|
// @Description 上传包含index.html的压缩包并解压到指定目录
|
||||||
|
// @Tags 文件上传
|
||||||
|
// @Accept multipart/form-data
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param file formData file true "压缩包文件"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/upload/zip [post]
|
||||||
|
func (h *SysUploadHandler) UploadZip(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UploadSvc.UploadZip(c))
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/internal/service"
|
||||||
|
"ego/internal/types"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysUserHandler struct {
|
||||||
|
UserSvc *service.SysUserService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysUserHandler 提供 SysUserHandler 的实例
|
||||||
|
func NewSysUserHandler(userSvc *service.SysUserService) *SysUserHandler {
|
||||||
|
return &SysUserHandler{
|
||||||
|
UserSvc: userSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建用户
|
||||||
|
// @Summary 创建用户
|
||||||
|
// @Description 创建新用户
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param user body model.SysUser true "用户信息"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser [post]
|
||||||
|
func (h *SysUserHandler) Create(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.Create(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID 删除用户
|
||||||
|
// @Summary 删除用户
|
||||||
|
// @Description 根据ID删除用户
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param id path string true "用户ID"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/{id} [delete]
|
||||||
|
func (h *SysUserHandler) DeleteByID(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.DeleteByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateByID 更新用户
|
||||||
|
// @Summary 更新用户
|
||||||
|
// @Description 根据ID更新用户信息
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param id path string true "用户ID"
|
||||||
|
// @Param user body model.SysUser true "用户信息"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/{id} [put]
|
||||||
|
func (h *SysUserHandler) UpdateByID(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.UpdateByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID查询用户
|
||||||
|
// @Summary 获取用户
|
||||||
|
// @Description 根据ID获取用户信息
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param id path string true "用户ID"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/{id} [get]
|
||||||
|
func (h *SysUserHandler) GetByID(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.GetByID(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByIDs 批量删除用户
|
||||||
|
// @Summary 批量删除用户
|
||||||
|
// @Description 根据ID列表批量删除用户
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body types.Payload true "ID列表"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/batch [delete]
|
||||||
|
func (h *SysUserHandler) DeleteByIDs(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.DeleteByIDs(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCondition 根据条件查询用户
|
||||||
|
// @Summary 条件查询用户
|
||||||
|
// @Description 根据条件查询用户列表
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param query body types.Params true "查询条件"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/condition [post]
|
||||||
|
func (h *SysUserHandler) GetByCondition(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.GetByCondition(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByIDs 根据ID列表查询用户
|
||||||
|
// @Summary 批量查询用户
|
||||||
|
// @Description 根据ID列表批量查询用户
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param ids body []string true "用户ID列表"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/list/ids [post]
|
||||||
|
func (h *SysUserHandler) ListByIDs(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.ListByIDs(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRegister 用户注册接口
|
||||||
|
// @Summary 用户注册
|
||||||
|
// @Description 新用户注册接口
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param user body service.UserRegisterRequest true "用户注册信息"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/user/register [post]
|
||||||
|
func (h *SysUserHandler) UserRegister(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.Register(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLogin 用户登录接口
|
||||||
|
// @Summary 用户登录
|
||||||
|
// @Description 用户登录接口
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param user body service.UserLoginRequest true "用户登录信息"
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/user/login [post]
|
||||||
|
func (h *SysUserHandler) UserLogin(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.Login(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserMe 用户详情
|
||||||
|
// @Summary 获取当前用户信息
|
||||||
|
// @Description 获取当前登录用户的详细信息
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/me [get]
|
||||||
|
func (h *SysUserHandler) UserMe(c *gin.Context) {
|
||||||
|
if user, err := h.UserSvc.CurrentUser(c); err == nil {
|
||||||
|
c.JSON(http.StatusOK, types.BuildUserResponse(*user))
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, serializer.Err(http.StatusInternalServerError, "获取用户信息失败!", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLogout 用户登出
|
||||||
|
// @Summary 用户登出
|
||||||
|
// @Description 用户退出登录
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Success 200 {object} serializer.Response
|
||||||
|
// @Router /api/v1/sysUser/logout [post]
|
||||||
|
func (h *SysUserHandler) UserLogout(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.UserSvc.UserLogout(c))
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "ego/docs"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cors 跨域配置(支持环境变量动态配置)
|
||||||
|
func Cors() gin.HandlerFunc {
|
||||||
|
config := cors.DefaultConfig()
|
||||||
|
|
||||||
|
// 设置基础配置
|
||||||
|
config.AllowMethods = []string{
|
||||||
|
"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS",
|
||||||
|
}
|
||||||
|
config.AllowHeaders = []string{
|
||||||
|
"Origin", "Content-Length", "Content-Type", "Cookie", "Authorization",
|
||||||
|
"X-Requested-With", "X-CSRF-Token", // 添加常用自定义头
|
||||||
|
}
|
||||||
|
config.AllowCredentials = true
|
||||||
|
config.MaxAge = 12 * time.Hour // 预检请求缓存12小时
|
||||||
|
|
||||||
|
// 根据环境配置允许的来源
|
||||||
|
if gin.Mode() == gin.ReleaseMode {
|
||||||
|
// 生产环境:从环境变量读取允许的域名(逗号分隔)
|
||||||
|
origins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
||||||
|
if origins == "" {
|
||||||
|
// 如果环境变量未设置,使用默认的安全配置
|
||||||
|
logger.Warn(nil, "CORS_ALLOWED_ORIGINS 环境变量未设置,使用默认安全配置")
|
||||||
|
config.AllowOrigins = []string{
|
||||||
|
"https://yourdomain.com", // 请替换为实际域名
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 清理空白字符并分割
|
||||||
|
originList := make([]string, 0)
|
||||||
|
for _, origin := range strings.Split(origins, ",") {
|
||||||
|
trimmed := strings.TrimSpace(origin)
|
||||||
|
if trimmed != "" {
|
||||||
|
originList = append(originList, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.AllowOrigins = originList
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 开发环境:匹配本地开发域名
|
||||||
|
config.AllowOriginFunc = func(origin string) bool {
|
||||||
|
// 匹配 http://localhost:端口 或 http://127.0.0.1:端口
|
||||||
|
re := regexp.MustCompile(`^http://(localhost|127\.0\.0\.1):\d+$`)
|
||||||
|
return re.MatchString(origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出当前生效的配置(方便调试)
|
||||||
|
if logger.Logger != nil {
|
||||||
|
logger.Info(nil, "跨域配置初始化完成",
|
||||||
|
zap.Any("允许方法", config.AllowMethods),
|
||||||
|
zap.Any("允许头", config.AllowHeaders),
|
||||||
|
zap.Any("允许来源", config.AllowOrigins),
|
||||||
|
zap.Duration("MaxAge", config.MaxAge),
|
||||||
|
zap.String("模式", gin.Mode()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cors.New(config)
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/internal/util"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthRequired 验证 JWT 的中间件
|
||||||
|
func AuthRequired() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 获取Authorization头
|
||||||
|
authHeader := c.GetHeader("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
sendUnauthorized(c, "缺少认证头")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Bearer前缀
|
||||||
|
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
|
sendUnauthorized(c, "无效的认证格式,请使用Bearer Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取token
|
||||||
|
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
|
if tokenString == "" {
|
||||||
|
sendUnauthorized(c, "Token不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证token
|
||||||
|
claims, err := util.ValidateToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "Token验证失败", zap.Error(err))
|
||||||
|
sendUnauthorized(c, "Token验证失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Redis中的token状态
|
||||||
|
redisKey := util.TokenGroup + claims.Username
|
||||||
|
val, err := util.Get(c, redisKey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "Redis验证失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("redisKey", redisKey))
|
||||||
|
sendUnauthorized(c, "登录状态验证失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == "" {
|
||||||
|
logger.Warn(c, "Token已过期或已注销",
|
||||||
|
zap.String("username", claims.Username))
|
||||||
|
sendUnauthorized(c, "登录状态已过期,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证Redis中的token是否与当前token一致
|
||||||
|
if val != tokenString {
|
||||||
|
logger.Warn(c, "Token不匹配,可能存在重复登录",
|
||||||
|
zap.String("username", claims.Username))
|
||||||
|
sendUnauthorized(c, "登录状态异常,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将用户信息存储到context中
|
||||||
|
c.Set("userID", claims.ID)
|
||||||
|
c.Set("username", claims.Username)
|
||||||
|
c.Set("id", claims.ID) // 保持向后兼容
|
||||||
|
|
||||||
|
logger.Debug(c, "JWT认证成功",
|
||||||
|
zap.String("userID", claims.ID),
|
||||||
|
zap.String("username", claims.Username))
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUnauthorized 发送401未授权响应
|
||||||
|
func sendUnauthorized(c *gin.Context, message string) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, serializer.Response{
|
||||||
|
Code: http.StatusUnauthorized,
|
||||||
|
Msg: message,
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/model"
|
||||||
|
"ego/internal/service"
|
||||||
|
"ego/internal/util"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoginLogMiddleware 登录日志中间件
|
||||||
|
func LoginLogMiddleware(db *gorm.DB) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 只处理登录请求
|
||||||
|
if c.Request.URL.Path != "/api/v1/user/login" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 执行登录逻辑
|
||||||
|
c.Next()
|
||||||
|
// 登录成功
|
||||||
|
recordLoginLog(c, db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recordLoginLog 记录登录日志
|
||||||
|
func recordLoginLog(c *gin.Context, db *gorm.DB) {
|
||||||
|
// 获取User-Agent
|
||||||
|
userAgent := c.GetHeader("User-Agent")
|
||||||
|
// 获取当前时间
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
var status, msg string
|
||||||
|
|
||||||
|
if c.GetString("status") == "1" {
|
||||||
|
status = "1"
|
||||||
|
msg = "登录成功"
|
||||||
|
} else {
|
||||||
|
status = "0"
|
||||||
|
msg = c.GetString("msg")
|
||||||
|
}
|
||||||
|
// 创建登录日志
|
||||||
|
loginLog := model.SysLoginLog{
|
||||||
|
UserId: c.GetString("account"),
|
||||||
|
IpAddr: c.ClientIP(),
|
||||||
|
LoginLocation: "", // TODO: 需要集成IP地理位置查询服务
|
||||||
|
Browser: userAgent,
|
||||||
|
Os: util.ParseUserAgent(userAgent),
|
||||||
|
Status: status,
|
||||||
|
Msg: msg,
|
||||||
|
LoginTime: &now,
|
||||||
|
UpdateTime: &now,
|
||||||
|
CreateTime: &now,
|
||||||
|
}
|
||||||
|
|
||||||
|
sequenceService := service.SysSequenceServiceBuilder(loginLog.TableName())
|
||||||
|
id, err := sequenceService.GenerateId()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "生成日志ID失败", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loginLog.Id = id
|
||||||
|
if err := db.Create(&loginLog).Error; err != nil {
|
||||||
|
logger.Error(c, "记录登录日志失败!", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnifiedErrorResponse 统一错误处理中间件
|
||||||
|
func UnifiedErrorResponse() gin.HandlerFunc {
|
||||||
|
return gin.CustomRecovery(func(c *gin.Context, err any) {
|
||||||
|
// 1. 记录完整错误日志
|
||||||
|
logger.Error(c, fmt.Sprintf("系统错误: %v", err))
|
||||||
|
// 2. 返回统一的Json风格
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, serializer.Err(http.StatusInternalServerError, "系统繁忙稍后重试!", err.(error)))
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GinZapLogger Gin 日志中间件
|
||||||
|
func GinZapLogger() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
trackID := logger.GetTrackID(c)
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// 记录请求日志
|
||||||
|
logger.Info(c, "Handled request",
|
||||||
|
zap.String("trackID", trackID),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.Int("status", c.Writer.Status()),
|
||||||
|
zap.Duration("duration", time.Since(start)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysDeployFile 部署文件记录表
|
||||||
|
type SysDeployFile struct {
|
||||||
|
DeployId string `gorm:"column:deploy_id;type:varchar(64);primary_key;comment:部署ID" json:"deployId"`
|
||||||
|
FileName string `gorm:"column:file_name;type:varchar(255);not null;comment:原始文件名" json:"fileName"`
|
||||||
|
ProjectName string `gorm:"column:project_name;type:varchar(100);not null;comment:项目名称" json:"projectName"`
|
||||||
|
Domain string `gorm:"column:domain;type:varchar(255);not null;comment:访问域名" json:"domain"`
|
||||||
|
DeployPath string `gorm:"column:deploy_path;type:varchar(500);not null;comment:部署路径" json:"deployPath"`
|
||||||
|
FileSize int64 `gorm:"column:file_size;type:bigint;comment:文件大小(字节)" json:"fileSize"`
|
||||||
|
FileHash string `gorm:"column:file_hash;type:varchar(64);comment:文件哈希值" json:"fileHash"`
|
||||||
|
Status string `gorm:"column:status;type:char(1);default:1;comment:状态(0停用 1正常 2部署中 3部署失败)" json:"status"`
|
||||||
|
DeployStatus string `gorm:"column:deploy_status;type:char(1);default:0;comment:部署状态(0未部署 1部署成功 2部署失败)" json:"deployStatus"`
|
||||||
|
ErrorMsg string `gorm:"column:error_msg;type:text;comment:错误信息" json:"errorMsg"`
|
||||||
|
Version string `gorm:"column:version;type:varchar(50);comment:版本号" json:"version"`
|
||||||
|
Description string `gorm:"column:description;type:varchar(500);comment:描述" json:"description"`
|
||||||
|
DelFlag string `gorm:"column:del_flag;type:char(1);default:0;comment:删除标志(0代表存在 1代表删除)" json:"delFlag"`
|
||||||
|
CreateBy string `gorm:"column:create_by;type:varchar(64);comment:创建者" json:"createBy"`
|
||||||
|
CreateTime *time.Time `gorm:"column:create_time;type:datetime;comment:创建时间" json:"createTime"`
|
||||||
|
UpdateBy string `gorm:"column:update_by;type:varchar(64);comment:更新者" json:"updateBy"`
|
||||||
|
UpdateTime *time.Time `gorm:"column:update_time;type:datetime;comment:更新时间" json:"updateTime"`
|
||||||
|
DeployTime *time.Time `gorm:"column:deploy_time;type:datetime;comment:部署时间" json:"deployTime"`
|
||||||
|
LastAccessTime *time.Time `gorm:"column:last_access_time;type:datetime;comment:最后访问时间" json:"lastAccessTime"`
|
||||||
|
AccessCount int64 `gorm:"column:access_count;type:bigint;default:0;comment:访问次数" json:"accessCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 表名
|
||||||
|
func (m *SysDeployFile) TableName() string {
|
||||||
|
return "sys_deploy_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeployFileStatus 部署文件状态常量
|
||||||
|
const (
|
||||||
|
DeployFileStatusDisabled = "0" // 停用
|
||||||
|
DeployFileStatusNormal = "1" // 正常
|
||||||
|
DeployFileStatusDeploying = "2" // 部署中
|
||||||
|
DeployFileStatusFailed = "3" // 部署失败
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeployStatus 部署状态常量
|
||||||
|
const (
|
||||||
|
DeployStatusNotDeployed = "0" // 未部署
|
||||||
|
DeployStatusSuccess = "1" // 部署成功
|
||||||
|
DeployStatusFailed = "2" // 部署失败
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SysLoginLog 系统登录日志
|
||||||
|
type SysLoginLog struct {
|
||||||
|
Id string `gorm:"column:id;type:varchar(64);primary_key;comment:主键" json:"id"`
|
||||||
|
UserId string `gorm:"column:user_id;type:varchar(64);comment:用户ID" json:"userId"`
|
||||||
|
IpAddr string `gorm:"column:ip_addr;type:varchar(128);comment:登录IP地址" json:"ipAddr"`
|
||||||
|
LoginLocation string `gorm:"column:login_location;type:varchar(255);comment:登录地点" json:"loginLocation"`
|
||||||
|
Browser string `gorm:"column:browser;type:varchar(128);comment:浏览器类型" json:"browser"`
|
||||||
|
Os string `gorm:"column:os;type:varchar(50);comment:操作系统" json:"os"`
|
||||||
|
Status string `gorm:"column:status;type:varchar(10);comment:登录状态(0成功 1失败)" json:"status"`
|
||||||
|
Msg string `gorm:"column:msg;type:varchar(255);comment:提示消息" json:"msg"`
|
||||||
|
LoginTime *time.Time `gorm:"column:login_time;type:varchar(20);comment:登录时间" json:"loginTime"`
|
||||||
|
CreateBy string `gorm:"column:create_by;type:varchar(64);comment:创建者" json:"createBy"`
|
||||||
|
CreateTime *time.Time `gorm:"column:create_time;type:datetime;comment:创建时间" json:"createTime"`
|
||||||
|
UpdateBy string `gorm:"column:update_by;type:varchar(64);comment:更新者" json:"updateBy"`
|
||||||
|
UpdateTime *time.Time `gorm:"column:update_time;type:datetime;comment:更新时间" json:"updateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 设置表名
|
||||||
|
func (SysLoginLog) TableName() string {
|
||||||
|
return "sys_login_log"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// SysSequence 系统序列表
|
||||||
|
type SysSequence struct {
|
||||||
|
Name string `gorm:"column:table_name;type:varchar(32);not null;comment:表名" json:"name"`
|
||||||
|
Seq int `gorm:"column:seq;type:int(11);not null;comment:序列" json:"seq"`
|
||||||
|
Prefix string `gorm:"column:Prefix;type:varchar(32);not null;comment:前缀" json:"prefix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName table name
|
||||||
|
func (m *SysSequence) TableName() string {
|
||||||
|
return "sys_sequence"
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysUser 用户信息表
|
||||||
|
type SysUser struct {
|
||||||
|
UserId string `gorm:"column:user_id;type:varchar(64);primary_key;comment:用户ID" json:"userId"`
|
||||||
|
DeptId string `gorm:"column:dept_id;type:varchar(64);comment:部门ID" json:"deptId"`
|
||||||
|
UserName string `gorm:"column:user_name;type:varchar(30);not null;comment:用户账号" json:"userName"`
|
||||||
|
NickName string `gorm:"column:nick_name;type:varchar(30);not null;comment:用户昵称" json:"nickName"`
|
||||||
|
UserType string `gorm:"column:user_type;type:varchar(2);default:00;comment:用户类型(00系统用户)" json:"userType"`
|
||||||
|
Email string `gorm:"column:email;type:varchar(50);comment:用户邮箱" json:"email"`
|
||||||
|
PhoneNumber string `gorm:"column:phone_number;type:varchar(11);comment:手机号码" json:"phoneNumber"`
|
||||||
|
Solt int `gorm:"column:solt;type:int(11);comment:排序" json:"solt"`
|
||||||
|
Gender string `gorm:"column:gender;type:char(1);default:0;comment:用户性别(0男 1女 2未知)" json:"gender"`
|
||||||
|
Avatar string `gorm:"column:avatar;type:varchar(100);comment:头像地址" json:"avatar"`
|
||||||
|
PassWord string `gorm:"column:pass_word;type:varchar(100);comment:密码" json:"passWord"`
|
||||||
|
Status string `gorm:"column:status;type:char(1);default:0;comment:帐号状态(0正常 1停用)" json:"status"`
|
||||||
|
DelFlag string `gorm:"column:del_flag;type:char(1);default:0;comment:删除标志(0代表存在 2代表删除)" json:"delFlag"`
|
||||||
|
LoginIP string `gorm:"column:login_ip;type:varchar(128);comment:最后登录IP" json:"loginIP"`
|
||||||
|
LoginDate *time.Time `gorm:"column:login_date;type:datetime;comment:最后登录时间" json:"loginDate"`
|
||||||
|
ResourceInvoke string `gorm:"column:resource_invoke;type:varchar(255);comment:资源来源映射,多个用,分割" json:"resourceInvoke"`
|
||||||
|
CreateBy string `gorm:"column:create_by;type:varchar(64);comment:创建者" json:"createBy"`
|
||||||
|
CreateTime *time.Time `gorm:"column:create_time;type:datetime;comment:创建时间" json:"createTime"`
|
||||||
|
UpdateBy string `gorm:"column:update_by;type:varchar(64);comment:更新者" json:"updateBy"`
|
||||||
|
UpdateTime *time.Time `gorm:"column:update_time;type:datetime;comment:更新时间" json:"updateTime"`
|
||||||
|
Remark string `gorm:"column:remark;type:varchar(500);comment:备注" json:"remark"`
|
||||||
|
SelectKey string `gorm:"column:select_key;type:varchar(64);comment:动态验证" json:"selectKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName table name
|
||||||
|
func (m *SysUser) TableName() string {
|
||||||
|
return "sys_user"
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/conf"
|
||||||
|
"ego/internal/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiRouterFns []func(r *gin.RouterGroup)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter 路由配置
|
||||||
|
func NewRouter() *gin.Engine {
|
||||||
|
// gin 实例
|
||||||
|
engine := gin.Default()
|
||||||
|
// 跨域处理
|
||||||
|
engine.Use(middleware.Cors())
|
||||||
|
// zap 日志
|
||||||
|
engine.Use(middleware.GinZapLogger())
|
||||||
|
// 统一错误处理
|
||||||
|
engine.Use(middleware.UnifiedErrorResponse())
|
||||||
|
// 登录日志中间件
|
||||||
|
engine.Use(middleware.LoginLogMiddleware(conf.Db))
|
||||||
|
|
||||||
|
// swagger
|
||||||
|
if gin.Mode() != gin.ReleaseMode {
|
||||||
|
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
}
|
||||||
|
// register routers, middleware support
|
||||||
|
registerRouters(engine, "/api/v1", apiRouterFns)
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerRouters 注册路由
|
||||||
|
func registerRouters(r *gin.Engine, groupPath string, routerFns []func(*gin.RouterGroup), handlers ...gin.HandlerFunc) {
|
||||||
|
rg := r.Group(groupPath, handlers...)
|
||||||
|
for _, fn := range routerFns {
|
||||||
|
fn(rg)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/handler"
|
||||||
|
"ego/internal/middleware"
|
||||||
|
"ego/internal/wire"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiRouterFns = append(apiRouterFns, func(group *gin.RouterGroup) {
|
||||||
|
SysDeployFileHandlerRouter(group, wire.InjectSysDeployFileHandler())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysDeployFileHandlerRouter 部署文件相关路由
|
||||||
|
// @Summary 部署文件管理路由
|
||||||
|
// @Description 包含部署文件的增删改查等接口
|
||||||
|
// @Tags 部署文件管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
func SysDeployFileHandlerRouter(group *gin.RouterGroup, h *handler.SysDeployFileHandler) {
|
||||||
|
// 部署文件管理路由组
|
||||||
|
g := group.Group("/deploy-files")
|
||||||
|
// 鉴权
|
||||||
|
g.Use(middleware.AuthRequired())
|
||||||
|
|
||||||
|
g.POST("/", h.Create)
|
||||||
|
g.GET("/:id", h.GetByID)
|
||||||
|
g.PUT("/", h.UpdateByID)
|
||||||
|
g.DELETE("/:id", h.DeleteByID)
|
||||||
|
g.GET("", h.GetByCondition)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/handler"
|
||||||
|
"ego/internal/middleware"
|
||||||
|
"ego/internal/wire"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @tag.name 登录记录
|
||||||
|
// @tag.description 登录记录相关的所有接口,包括创建、删除、更新、查询等
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiRouterFns = append(apiRouterFns, func(group *gin.RouterGroup) {
|
||||||
|
SysLoginLogRouter(group, wire.InjectSysLoginLogHandler())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysLoginLogRouter 登录日志路由
|
||||||
|
func SysLoginLogRouter(r *gin.RouterGroup, h *handler.SysLoginLogHandler) {
|
||||||
|
g := r.Group("/sysLoginLog")
|
||||||
|
// 登录日志需要认证
|
||||||
|
g.Use(middleware.AuthRequired())
|
||||||
|
{
|
||||||
|
g.POST("/create", h.Create)
|
||||||
|
g.POST("/delete", h.DeleteByID)
|
||||||
|
g.POST("/update", h.UpdateByID)
|
||||||
|
g.POST("/get", h.GetByID)
|
||||||
|
g.POST("/list", h.GetByCondition)
|
||||||
|
g.POST("/list/ids", h.ListByIDs)
|
||||||
|
g.POST("/delete/ids", h.DeleteByIDs)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/handler"
|
||||||
|
"ego/internal/wire"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiRouterFns = append(apiRouterFns, func(group *gin.RouterGroup) {
|
||||||
|
SysUploadHandlerRouter(group, wire.InjectSysUploadHandler())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysUploadHandlerRouter 文件上传路由
|
||||||
|
func SysUploadHandlerRouter(r *gin.RouterGroup, h *handler.SysUploadHandler) {
|
||||||
|
upload := r.Group("/upload")
|
||||||
|
//upload.Use(middleware.AuthRequired())
|
||||||
|
{
|
||||||
|
upload.POST("/zip", h.UploadZip)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/handler"
|
||||||
|
"ego/internal/middleware"
|
||||||
|
"ego/internal/wire"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @title EGO API
|
||||||
|
// @version 1.0
|
||||||
|
// @description EGO 系统 API 文档
|
||||||
|
// @termsOfService http://swagger.io/terms/
|
||||||
|
|
||||||
|
// @tag.name 用户管理
|
||||||
|
// @tag.description 用户相关的所有接口,包括注册、登录、信息管理等
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiRouterFns = append(apiRouterFns, func(group *gin.RouterGroup) {
|
||||||
|
SysUserHandlerRouter(group, wire.InjectSysUserHandler())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysUserHandlerRouter 用户相关路由
|
||||||
|
// @Summary 用户管理路由
|
||||||
|
// @Description 包含用户注册、登录、信息管理等接口
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
func SysUserHandlerRouter(group *gin.RouterGroup, h *handler.SysUserHandler) {
|
||||||
|
// 不需要认证
|
||||||
|
rg := group.Group("/user")
|
||||||
|
rg.POST("/register", h.UserRegister)
|
||||||
|
rg.POST("/login", h.UserLogin)
|
||||||
|
|
||||||
|
g := group.Group("/sysUser")
|
||||||
|
// 鉴权
|
||||||
|
g.Use(middleware.AuthRequired())
|
||||||
|
|
||||||
|
g.POST("/", h.Create)
|
||||||
|
g.DELETE("/:id", h.DeleteByID)
|
||||||
|
g.PUT("/", h.UpdateByID)
|
||||||
|
g.GET("/:id", h.GetByID)
|
||||||
|
g.DELETE("/batch", h.DeleteByIDs)
|
||||||
|
|
||||||
|
g.POST("/condition", h.GetByCondition)
|
||||||
|
g.POST("/list/ids", h.ListByIDs)
|
||||||
|
g.GET("/me", h.UserMe)
|
||||||
|
|
||||||
|
g.POST("/logout", h.UserLogout)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package serializer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Response 基础序列化器
|
||||||
|
type Response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data any `json:"data,omitempty"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackedErrorResponse 有追踪信息的错误响应
|
||||||
|
type TrackedErrorResponse struct {
|
||||||
|
Response
|
||||||
|
TrackID string `json:"track_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
CodeCheckLogin = 401
|
||||||
|
CodeNoRightErr = 403
|
||||||
|
CodeDBError = 50001
|
||||||
|
CodeEncryptError = 50002
|
||||||
|
CodeParamErr = 40001
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckLogin 检查登录
|
||||||
|
func CheckLogin() Response {
|
||||||
|
return Err(CodeCheckLogin, "未登录", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err 通用错误处理
|
||||||
|
func Err(errCode int, msg string, err error) Response {
|
||||||
|
if msg == "" {
|
||||||
|
msg = "未知错误"
|
||||||
|
}
|
||||||
|
res := Response{
|
||||||
|
Code: errCode,
|
||||||
|
Msg: msg,
|
||||||
|
}
|
||||||
|
if err != nil && gin.Mode() != gin.ReleaseMode {
|
||||||
|
res.Error = fmt.Sprintf("%+v", err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBErr 数据库操作失败
|
||||||
|
func DBErr(msg string, err error) Response {
|
||||||
|
if msg == "" {
|
||||||
|
msg = "数据库操作失败"
|
||||||
|
}
|
||||||
|
return Err(CodeDBError, msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamErr 各种参数错误
|
||||||
|
func ParamErr(msg string, err error) Response {
|
||||||
|
if msg == "" {
|
||||||
|
msg = "参数错误"
|
||||||
|
}
|
||||||
|
return Err(CodeParamErr, msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Succ 返回结果
|
||||||
|
func Succ(msg string, data any) Response {
|
||||||
|
if msg == "" {
|
||||||
|
msg = "操作成功!"
|
||||||
|
}
|
||||||
|
return Response{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
Msg: msg,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"ego/internal/model"
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/internal/types"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysDeployFileService 部署文件服务
|
||||||
|
type SysDeployFileService struct {
|
||||||
|
Db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysDeployFileService 构建部署文件服务
|
||||||
|
func NewSysDeployFileService(db *gorm.DB) *SysDeployFileService {
|
||||||
|
return &SysDeployFileService{
|
||||||
|
Db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建部署文件记录
|
||||||
|
func (s *SysDeployFileService) Create(c *gin.Context) serializer.Response {
|
||||||
|
var deployFile model.SysDeployFile
|
||||||
|
if err := c.ShouldBind(&deployFile); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成部署ID
|
||||||
|
if id, err := SysSequenceServiceBuilder(deployFile.TableName()).GenerateId(); err == nil {
|
||||||
|
deployFile.DeployId = id
|
||||||
|
} else {
|
||||||
|
return serializer.DBErr("序列生成失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
now := time.Now()
|
||||||
|
deployFile.CreateTime = &now
|
||||||
|
deployFile.Status = model.DeployFileStatusNormal
|
||||||
|
deployFile.DeployStatus = model.DeployStatusNotDeployed
|
||||||
|
deployFile.DelFlag = "0"
|
||||||
|
|
||||||
|
// 获取当前用户
|
||||||
|
if createBy := c.GetString("id"); createBy != "" {
|
||||||
|
deployFile.CreateBy = createBy
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Create(&deployFile).Error; err != nil {
|
||||||
|
logger.Error(c, "创建部署文件记录失败!")
|
||||||
|
return serializer.DBErr("创建部署文件记录失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("创建部署文件记录成功!", deployFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID获取部署文件记录
|
||||||
|
func (s *SysDeployFileService) GetByID(c *gin.Context) serializer.Response {
|
||||||
|
var deployFile model.SysDeployFile
|
||||||
|
if err := s.Db.Where("deploy_id = ? AND del_flag = ?", c.Param("id"), "0").First(&deployFile).Error; err != nil {
|
||||||
|
logger.Error(c, "获取部署文件记录失败!")
|
||||||
|
return serializer.DBErr("获取部署文件记录失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("查询成功!", deployFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateByID 根据ID更新部署文件记录
|
||||||
|
func (s *SysDeployFileService) UpdateByID(c *gin.Context) serializer.Response {
|
||||||
|
var deployFile model.SysDeployFile
|
||||||
|
if err := c.ShouldBind(&deployFile); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := deployFile.DeployId
|
||||||
|
if id == "" {
|
||||||
|
logger.Error(c, "id 不可为空!")
|
||||||
|
return serializer.ParamErr("id不可为空!", fmt.Errorf("id不可为空"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置更新时间
|
||||||
|
now := time.Now()
|
||||||
|
deployFile.UpdateTime = &now
|
||||||
|
|
||||||
|
// 获取当前用户
|
||||||
|
if updateBy := c.GetString("id"); updateBy != "" {
|
||||||
|
deployFile.UpdateBy = updateBy
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Model(&deployFile).Where("deploy_id = ? AND del_flag = ?", id, "0").Updates(&deployFile).Error; err != nil {
|
||||||
|
logger.Error(c, "更新部署文件记录失败!")
|
||||||
|
return serializer.DBErr("更新部署文件记录失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("更新部署文件记录成功!", deployFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID 根据ID删除部署文件记录
|
||||||
|
func (s *SysDeployFileService) DeleteByID(c *gin.Context) serializer.Response {
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
logger.Error(c, "id 不可为空!")
|
||||||
|
return serializer.ParamErr("id不可为空!", fmt.Errorf("id不可为空"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 软删除
|
||||||
|
data := map[string]any{
|
||||||
|
"del_flag": "1",
|
||||||
|
"update_time": time.Now(),
|
||||||
|
"update_by": c.GetString("id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Model(&model.SysDeployFile{}).Where("deploy_id = ?", id).Updates(data).Error; err != nil {
|
||||||
|
logger.Error(c, "删除部署文件记录失败!")
|
||||||
|
return serializer.DBErr("删除部署文件记录失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("删除部署文件记录成功!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCondition 条件查询部署文件记录
|
||||||
|
func (s *SysDeployFileService) GetByCondition(c *gin.Context) serializer.Response {
|
||||||
|
var p types.Params
|
||||||
|
if err := c.ShouldBind(&p); err != nil {
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryStr, args, err := p.ConvertToGormConditions()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
var deployFiles []model.SysDeployFile
|
||||||
|
offset := (p.Page - 1) * p.Limit
|
||||||
|
|
||||||
|
// 构建基础查询
|
||||||
|
db := s.Db.Model(&model.SysDeployFile{})
|
||||||
|
|
||||||
|
// 如果有查询条件,添加条件
|
||||||
|
if queryStr != "" {
|
||||||
|
db = db.Where(queryStr, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
if p.Sort != "" {
|
||||||
|
db = db.Order(p.Sort)
|
||||||
|
} else {
|
||||||
|
db = db.Order("create_time DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
if err := db.Where("del_flag = ?", "0").Offset(offset).Limit(p.Limit).Find(&deployFiles).Error; err != nil {
|
||||||
|
logger.Error(c, "获取部署文件记录失败!")
|
||||||
|
return serializer.DBErr("获取部署文件记录失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行总数查询
|
||||||
|
if err := db.Where("del_flag = ?", "0").Count(&total).Error; err != nil {
|
||||||
|
logger.Error(c, "获取部署文件记录总数失败!")
|
||||||
|
return serializer.DBErr("获取部署文件记录总数失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("查询成功!", gin.H{
|
||||||
|
"total": total,
|
||||||
|
"items": deployFiles,
|
||||||
|
"page": p.Page,
|
||||||
|
"limit": p.Limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDeployStatus 更新部署状态
|
||||||
|
func (s *SysDeployFileService) UpdateDeployStatus(deployId, status, errorMsg string) error {
|
||||||
|
data := map[string]any{
|
||||||
|
"deploy_status": status,
|
||||||
|
"update_time": time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == model.DeployStatusSuccess {
|
||||||
|
now := time.Now()
|
||||||
|
data["deploy_time"] = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorMsg != "" {
|
||||||
|
data["error_msg"] = errorMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Db.Model(&model.SysDeployFile{}).Where("deploy_id = ?", deployId).Updates(data).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessInfo 更新访问信息
|
||||||
|
func (s *SysDeployFileService) UpdateAccessInfo(deployId string) error {
|
||||||
|
now := time.Now()
|
||||||
|
return s.Db.Model(&model.SysDeployFile{}).
|
||||||
|
Where("deploy_id = ?", deployId).
|
||||||
|
Updates(map[string]any{
|
||||||
|
"last_access_time": &now,
|
||||||
|
"access_count": gorm.Expr("access_count + 1"),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByDomain 根据域名获取部署文件记录
|
||||||
|
func (s *SysDeployFileService) GetByDomain(domain string) (*model.SysDeployFile, error) {
|
||||||
|
var deployFile model.SysDeployFile
|
||||||
|
err := s.Db.Where("domain = ? AND status = ? AND del_flag = ?",
|
||||||
|
domain, model.DeployFileStatusNormal, "0").First(&deployFile).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &deployFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateFileHash 计算文件哈希值
|
||||||
|
func (s *SysDeployFileService) CalculateFileHash(reader io.Reader) (string, error) {
|
||||||
|
hash := md5.New()
|
||||||
|
if _, err := io.Copy(hash, reader); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/model"
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/internal/types"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysLoginLogService 登录日志服务
|
||||||
|
type SysLoginLogService struct {
|
||||||
|
Db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysLoginLogService 构建登录日志服务
|
||||||
|
func NewSysLoginLogService(db *gorm.DB) *SysLoginLogService {
|
||||||
|
return &SysLoginLogService{
|
||||||
|
Db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建登录日志
|
||||||
|
func (s *SysLoginLogService) Create(c *gin.Context) serializer.Response {
|
||||||
|
var loginLog model.SysLoginLog
|
||||||
|
if err := c.ShouldBind(&loginLog); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成登录日志ID
|
||||||
|
id, err := SysSequenceServiceBuilder(loginLog.TableName()).GenerateId()
|
||||||
|
if err != nil {
|
||||||
|
return serializer.DBErr("创建登录日志失败!", err)
|
||||||
|
}
|
||||||
|
loginLog.Id = id
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
loginLog.LoginTime = &now
|
||||||
|
|
||||||
|
if err := s.Db.Create(&loginLog).Error; err != nil {
|
||||||
|
logger.Error(c, "创建登录日志失败!")
|
||||||
|
return serializer.DBErr("创建登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("创建登录日志成功!", loginLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID 根据ID删除登录日志
|
||||||
|
func (s *SysLoginLogService) DeleteByID(c *gin.Context) serializer.Response {
|
||||||
|
var loginLog model.SysLoginLog
|
||||||
|
if err := c.ShouldBind(&loginLog); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Delete(&loginLog).Error; err != nil {
|
||||||
|
logger.Error(c, "删除登录日志失败!")
|
||||||
|
return serializer.DBErr("删除登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("删除登录日志成功!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateByID 根据ID更新登录日志
|
||||||
|
func (s *SysLoginLogService) UpdateByID(c *gin.Context) serializer.Response {
|
||||||
|
var loginLog model.SysLoginLog
|
||||||
|
if err := c.ShouldBind(&loginLog); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Model(&loginLog).Updates(loginLog).Error; err != nil {
|
||||||
|
logger.Error(c, "更新登录日志失败!")
|
||||||
|
return serializer.DBErr("更新登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("更新登录日志成功!", loginLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID获取登录日志
|
||||||
|
func (s *SysLoginLogService) GetByID(c *gin.Context) serializer.Response {
|
||||||
|
var loginLog model.SysLoginLog
|
||||||
|
if err := c.ShouldBind(&loginLog); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.First(&loginLog).Error; err != nil {
|
||||||
|
logger.Error(c, "获取登录日志失败!")
|
||||||
|
return serializer.DBErr("获取登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("获取登录日志成功!", loginLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCondition 根据条件获取登录日志
|
||||||
|
func (s *SysLoginLogService) GetByCondition(c *gin.Context) serializer.Response {
|
||||||
|
var loginLog model.SysLoginLog
|
||||||
|
if err := c.ShouldBind(&loginLog); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginLogs []model.SysLoginLog
|
||||||
|
if err := s.Db.Where(&loginLog).Find(&loginLogs).Error; err != nil {
|
||||||
|
logger.Error(c, "获取登录日志失败!")
|
||||||
|
return serializer.DBErr("获取登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("获取登录日志成功!", loginLogs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByIDs 根据ID列表获取登录日志
|
||||||
|
func (s *SysLoginLogService) ListByIDs(c *gin.Context) serializer.Response {
|
||||||
|
var ids types.Payload
|
||||||
|
if err := c.ShouldBind(&ids); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginLogs []model.SysLoginLog
|
||||||
|
if err := s.Db.Where("id IN ?", ids.Ids).Find(&loginLogs).Error; err != nil {
|
||||||
|
logger.Error(c, "获取登录日志失败!")
|
||||||
|
return serializer.DBErr("获取登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("获取登录日志成功!", loginLogs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByIDs 根据ID列表删除登录日志
|
||||||
|
func (s *SysLoginLogService) DeleteByIDs(c *gin.Context) serializer.Response {
|
||||||
|
var ids types.Payload
|
||||||
|
if err := c.ShouldBind(&ids); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Where("id IN ?", ids.Ids).Delete(&model.SysLoginLog{}).Error; err != nil {
|
||||||
|
logger.Error(c, "删除登录日志失败!")
|
||||||
|
return serializer.DBErr("删除登录日志失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("删除登录日志成功!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogging 创建登录日志
|
||||||
|
func (s *SysLoginLogService) CreateLogging(sysLoginLog *model.SysLoginLog, c *gin.Context) error {
|
||||||
|
// 生成登录日志ID
|
||||||
|
id, err := SysSequenceServiceBuilder(sysLoginLog.TableName()).GenerateId()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("生成登录日志ID失败: %v", err)
|
||||||
|
}
|
||||||
|
sysLoginLog.Id = id
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
sysLoginLog.LoginTime = &now
|
||||||
|
|
||||||
|
if err := s.Db.Create(sysLoginLog).Error; err != nil {
|
||||||
|
logger.Error(c, "创建登录日志失败!")
|
||||||
|
return fmt.Errorf("创建登录日志失败: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/conf"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysSequenceService struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
tableName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysSequenceServiceBuilder 创建一个SysSequenceService
|
||||||
|
func SysSequenceServiceBuilder(tableName string) *SysSequenceService {
|
||||||
|
return &SysSequenceService{
|
||||||
|
tableName: tableName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateId 根据表名生成ID(使用序列)
|
||||||
|
func (s *SysSequenceService) GenerateId() (string, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
var id string
|
||||||
|
err := conf.Db.Raw("SELECT NEXTVAL(?)", strings.ToLower(s.tableName)).Scan(&id).Error
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"crypto/md5"
|
||||||
|
"ego/internal/model"
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysUploadService 文件上传服务
|
||||||
|
type SysUploadService struct {
|
||||||
|
Db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysUploadService 构建文件上传服务
|
||||||
|
func NewSysUploadService(db *gorm.DB) *SysUploadService {
|
||||||
|
return &SysUploadService{
|
||||||
|
Db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadZip 上传压缩包
|
||||||
|
func (s *SysUploadService) UploadZip(c *gin.Context) serializer.Response {
|
||||||
|
// 获取上传的文件
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "获取上传文件失败!")
|
||||||
|
return serializer.ParamErr("获取上传文件失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验文件类型
|
||||||
|
if !strings.HasSuffix(strings.ToLower(file.Filename), ".zip") {
|
||||||
|
logger.Error(c, "只支持zip格式文件!")
|
||||||
|
return serializer.ParamErr("只支持zip格式文件!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件名(不包含扩展名)
|
||||||
|
filename := strings.TrimSuffix(file.Filename, filepath.Ext(file.Filename))
|
||||||
|
|
||||||
|
// 检查目标文件夹是否存在
|
||||||
|
targetDir := filepath.Join("/home", filename)
|
||||||
|
if _, err := os.Stat(targetDir); !os.IsNotExist(err) {
|
||||||
|
logger.Error(c, "目标文件夹已存在!")
|
||||||
|
return serializer.ParamErr("目标文件夹已存在!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开上传的文件
|
||||||
|
src, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "打开上传文件失败!")
|
||||||
|
return serializer.ParamErr("打开上传文件失败!", err)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
// 创建临时文件
|
||||||
|
tempFile, err := os.CreateTemp("", "upload-*.zip")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "创建临时文件失败!")
|
||||||
|
return serializer.ParamErr("创建临时文件失败!", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
// 复制文件内容到临时文件
|
||||||
|
if _, err := io.Copy(tempFile, src); err != nil {
|
||||||
|
logger.Error(c, "复制文件内容失败!")
|
||||||
|
return serializer.ParamErr("复制文件内容失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验压缩包是否包含index.html
|
||||||
|
hasIndexHtml, err := s.validateZipFile(tempFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "校验压缩包失败!")
|
||||||
|
return serializer.ParamErr("校验压缩包失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasIndexHtml {
|
||||||
|
logger.Error(c, "压缩包必须包含index.html文件!")
|
||||||
|
return serializer.ParamErr("压缩包必须包含index.html文件!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建目标目录
|
||||||
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||||
|
logger.Error(c, "创建目标目录失败!")
|
||||||
|
return serializer.ParamErr("创建目标目录失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解压文件
|
||||||
|
if err := s.extractZip(tempFile.Name(), targetDir); err != nil {
|
||||||
|
// 如果解压失败,删除已创建的目录
|
||||||
|
os.RemoveAll(targetDir)
|
||||||
|
logger.Error(c, "解压文件失败!")
|
||||||
|
return serializer.ParamErr("解压文件失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回域名格式的结果
|
||||||
|
domain := fmt.Sprintf("%s.unbug.cn", filename)
|
||||||
|
|
||||||
|
// 创建部署文件记录
|
||||||
|
deployFile := model.SysDeployFile{
|
||||||
|
FileName: file.Filename,
|
||||||
|
ProjectName: filename,
|
||||||
|
Domain: domain,
|
||||||
|
DeployPath: targetDir,
|
||||||
|
FileSize: file.Size,
|
||||||
|
Status: model.DeployFileStatusNormal,
|
||||||
|
DeployStatus: model.DeployStatusSuccess,
|
||||||
|
Description: fmt.Sprintf("自动部署项目: %s", filename),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成部署ID
|
||||||
|
if id, err := SysSequenceServiceBuilder(deployFile.TableName()).GenerateId(); err == nil {
|
||||||
|
deployFile.DeployId = id
|
||||||
|
} else {
|
||||||
|
logger.Error(c, "生成部署ID失败!", zap.Error(err))
|
||||||
|
return serializer.DBErr("生成部署ID失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算文件哈希值
|
||||||
|
if fileHash, err := s.calculateFileHash(tempFile.Name()); err == nil {
|
||||||
|
deployFile.FileHash = fileHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置时间
|
||||||
|
now := time.Now()
|
||||||
|
deployFile.CreateTime = &now
|
||||||
|
deployFile.DeployTime = &now
|
||||||
|
deployFile.DelFlag = "0"
|
||||||
|
|
||||||
|
// 获取当前用户
|
||||||
|
if createBy := c.GetString("id"); createBy != "" {
|
||||||
|
deployFile.CreateBy = createBy
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到数据库
|
||||||
|
if err := s.Db.Create(&deployFile).Error; err != nil {
|
||||||
|
logger.Error(c, "保存部署文件记录失败!", zap.Error(err))
|
||||||
|
// 即使保存记录失败,也不影响文件部署
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("上传成功!", map[string]interface{}{
|
||||||
|
"domain": domain,
|
||||||
|
"path": targetDir,
|
||||||
|
"deployId": deployFile.DeployId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateZipFile 校验压缩包是否包含index.html
|
||||||
|
func (s *SysUploadService) validateZipFile(zipPath string) (bool, error) {
|
||||||
|
reader, err := zip.OpenReader(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
for _, file := range reader.File {
|
||||||
|
if strings.ToLower(filepath.Base(file.Name)) == "index.html" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractZip 解压zip文件到指定目录
|
||||||
|
func (s *SysUploadService) extractZip(zipPath, destDir string) error {
|
||||||
|
reader, err := zip.OpenReader(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
// 创建目标目录
|
||||||
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取文件
|
||||||
|
for _, file := range reader.File {
|
||||||
|
rc, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
// 构建文件路径
|
||||||
|
path := filepath.Join(destDir, file.Name)
|
||||||
|
|
||||||
|
// 检查路径是否安全(防止路径遍历攻击)
|
||||||
|
if !strings.HasPrefix(path, filepath.Clean(destDir)+string(os.PathSeparator)) {
|
||||||
|
return fmt.Errorf("invalid file path: %s", file.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.FileInfo().IsDir() {
|
||||||
|
// 创建目录
|
||||||
|
if err := os.MkdirAll(path, file.FileInfo().Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 创建文件的父目录
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建文件
|
||||||
|
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.FileInfo().Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
// 复制文件内容
|
||||||
|
if _, err := io.Copy(outFile, rc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateFileHash 计算文件哈希值
|
||||||
|
func (s *SysUploadService) calculateFileHash(filePath string) (string, error) {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
hash := md5.New()
|
||||||
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||||
|
}
|
|
@ -0,0 +1,427 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/model"
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"ego/internal/types"
|
||||||
|
"ego/internal/util"
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PassWordCost 密码加密难度
|
||||||
|
PassWordCost = 12
|
||||||
|
// Suspend 被封禁用户
|
||||||
|
Suspend = "-1"
|
||||||
|
// Active 未激活用户
|
||||||
|
Active = "1"
|
||||||
|
// Inactive 激活用户
|
||||||
|
Inactive = "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysUserService 管理用户登录的服务
|
||||||
|
type SysUserService struct {
|
||||||
|
Db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSysUserService 构建用户登录服务
|
||||||
|
func NewSysUserService(db *gorm.DB) *SysUserService {
|
||||||
|
return &SysUserService{
|
||||||
|
Db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLoginRequest struct {
|
||||||
|
Account string `form:"account" json:"account" binding:"required,min=5,max=30"`
|
||||||
|
PassWord string `form:"password" json:"password" binding:"required,min=8,max=40"`
|
||||||
|
Checked *bool `form:"checked" json:"checked" binding:"required"`
|
||||||
|
Phone string `form:"phone" json:"phone"`
|
||||||
|
VerifyCode string `form:"verifyCode" json:"verifyCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRegisterRequest 用户注册表单验证
|
||||||
|
type UserRegisterRequest struct {
|
||||||
|
NickName string `form:"nickName" json:"nickName" binding:"required,min=2,max=30"`
|
||||||
|
UserName string `form:"userName" json:"userName" binding:"required,min=5,max=30"`
|
||||||
|
PassWord string `form:"passWord" json:"passWord" binding:"required,min=8,max=40"`
|
||||||
|
PasswordConfirm string `form:"passWordConfirm" json:"passWordConfirm" binding:"required,min=8,max=40"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login 用户登录函数
|
||||||
|
func (s *SysUserService) Login(c *gin.Context) serializer.Response {
|
||||||
|
var user model.SysUser
|
||||||
|
u := UserLoginRequest{}
|
||||||
|
if err := c.ShouldBind(&u); err != nil {
|
||||||
|
logger.Error(c, "参数绑定错误!", zap.Error(err))
|
||||||
|
c.Set("msg", "参数绑定错误!")
|
||||||
|
return serializer.Err(serializer.CodeParamErr, "参数绑定错误!", err)
|
||||||
|
}
|
||||||
|
c.Set("account", u.Account)
|
||||||
|
if err := s.Db.Where("user_name = ?", u.Account).First(&user).Error; err != nil {
|
||||||
|
logger.Error(c, "账号或密码错误!")
|
||||||
|
c.Set("msg", "账号或密码错误!")
|
||||||
|
return serializer.ParamErr("账号或密码错误!", nil)
|
||||||
|
}
|
||||||
|
if user.Status == Suspend {
|
||||||
|
logger.Error(c, "账号已封禁!")
|
||||||
|
c.Set("msg", "账号已封禁!")
|
||||||
|
return serializer.ParamErr("账号已封禁!", nil)
|
||||||
|
}
|
||||||
|
if user.Status == Inactive {
|
||||||
|
logger.Error(c, "账号未激活!")
|
||||||
|
c.Set("msg", "账号未激活!")
|
||||||
|
return serializer.ParamErr("账号未激活!", nil)
|
||||||
|
}
|
||||||
|
if !CheckPassWord(u.PassWord, user.PassWord) {
|
||||||
|
logger.Error(c, "账号或密码错误!")
|
||||||
|
c.Set("msg", "账号或密码错误!")
|
||||||
|
return serializer.ParamErr("账号或密码错误!", nil)
|
||||||
|
}
|
||||||
|
if token, err := util.GenerateToken(user.UserId, user.UserName); err == nil {
|
||||||
|
logger.Info(c, "用户登录", zap.String("redisKey", util.TokenGroup+user.UserName))
|
||||||
|
// 删除key
|
||||||
|
del := util.Del(c, util.TokenGroup+user.UserName)
|
||||||
|
if del != nil {
|
||||||
|
logger.Error(c, "redis 删除失败!", zap.Error(del))
|
||||||
|
c.Set("msg", "redis 删除失败!")
|
||||||
|
return serializer.ParamErr("系统繁忙稍后重试!", nil)
|
||||||
|
}
|
||||||
|
// 结果放入redis
|
||||||
|
set := util.Set(c, util.TokenGroup+user.UserName, token, time.Hour*24)
|
||||||
|
if set != nil {
|
||||||
|
logger.Error(c, "redis 放入失败!", zap.Error(set))
|
||||||
|
c.Set("msg", "redis 放入失败!")
|
||||||
|
return serializer.ParamErr("系统繁忙稍后重试!", nil)
|
||||||
|
}
|
||||||
|
c.Set("status", Active)
|
||||||
|
return types.BuildUserResponseHasToken(user, token)
|
||||||
|
}
|
||||||
|
return serializer.ParamErr("系统繁忙稍后重试!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLogout 退出登录
|
||||||
|
func (s *SysUserService) UserLogout(c *gin.Context) serializer.Response {
|
||||||
|
// 退出登录
|
||||||
|
go func() {
|
||||||
|
tokenString := c.GetHeader("Authorization")
|
||||||
|
token, err := util.ParseToken(tokenString[8:])
|
||||||
|
if err == nil {
|
||||||
|
username := token["username"].(string)
|
||||||
|
// 删除key
|
||||||
|
if err := util.Del(c, util.TokenGroup+username); err != nil {
|
||||||
|
logger.Error(c, "退出登录失败!", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logger.Info(c, "用户退出登录成功!", zap.String("redisKey", util.TokenGroup+username))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return serializer.Succ("退出登录成功!", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser 用ID获取用户
|
||||||
|
func GetUser(id string, d *gorm.DB) (model.SysUser, error) {
|
||||||
|
var user model.SysUser
|
||||||
|
result := d.Where("user_id = ?", id).First(&user)
|
||||||
|
if result.Error == nil {
|
||||||
|
user.PassWord = "" // 清除密码
|
||||||
|
}
|
||||||
|
return user, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPassWord 设置密码
|
||||||
|
func SetPassWord(passWord string, sysUser *model.SysUser) error {
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(passWord), PassWordCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sysUser.PassWord = string(bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPassWord 校验密码
|
||||||
|
func CheckPassWord(password string, passwordDigest string) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(passwordDigest), []byte(password))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid 验证表单
|
||||||
|
func (s *SysUserService) valid(u *UserRegisterRequest) *serializer.Response {
|
||||||
|
if u.PasswordConfirm != u.PassWord {
|
||||||
|
return &serializer.Response{
|
||||||
|
Code: 40001,
|
||||||
|
Msg: "两次输入的密码不相同",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count := int64(0)
|
||||||
|
s.Db.Model(&model.SysUser{}).Where("nick_name = ?", u.NickName).Count(&count)
|
||||||
|
if count > 0 {
|
||||||
|
return &serializer.Response{
|
||||||
|
Code: 40001,
|
||||||
|
Msg: "昵称被占用",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
s.Db.Model(&model.SysUser{}).Where("user_name = ?", u.UserName).Count(&count)
|
||||||
|
if count > 0 {
|
||||||
|
return &serializer.Response{
|
||||||
|
Code: 40001,
|
||||||
|
Msg: "用户名已经注册",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 用户注册
|
||||||
|
func (s *SysUserService) Register(c *gin.Context) serializer.Response {
|
||||||
|
u := UserRegisterRequest{}
|
||||||
|
if err := c.ShouldBind(&u); err != nil {
|
||||||
|
return serializer.Err(serializer.CodeParamErr, "参数绑定错误!", err)
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
user := model.SysUser{
|
||||||
|
NickName: u.NickName,
|
||||||
|
UserName: u.UserName,
|
||||||
|
Status: Active,
|
||||||
|
CreateTime: &now,
|
||||||
|
}
|
||||||
|
// 生成ID
|
||||||
|
if id, err := SysSequenceServiceBuilder(user.TableName()).GenerateId(); err == nil {
|
||||||
|
user.UserId = id
|
||||||
|
} else {
|
||||||
|
return serializer.DBErr("序列生成失败!", err)
|
||||||
|
}
|
||||||
|
// 表单验证
|
||||||
|
if err := s.valid(&u); err != nil {
|
||||||
|
logger.Error(c, err.Msg, zap.String("NickName", u.NickName), zap.String("UserName", u.UserName))
|
||||||
|
return *err
|
||||||
|
}
|
||||||
|
// 密码加密
|
||||||
|
if err := SetPassWord(u.PassWord, &user); err != nil {
|
||||||
|
logger.Error(c, "密码加密失败!")
|
||||||
|
return serializer.Err(serializer.CodeEncryptError, "密码加密失败!", err)
|
||||||
|
}
|
||||||
|
// 使用事务封装数据库操作
|
||||||
|
err := s.Db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Create(&user).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "注册失败!")
|
||||||
|
return serializer.ParamErr("注册失败", err)
|
||||||
|
}
|
||||||
|
return types.BuildUserResponse(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建用户
|
||||||
|
func (s *SysUserService) Create(c *gin.Context) serializer.Response {
|
||||||
|
var user model.SysUser
|
||||||
|
if err := c.ShouldBind(&user); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Status == "" {
|
||||||
|
user.Status = Active
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
user.CreateTime = &now
|
||||||
|
|
||||||
|
createBy := c.GetString("id")
|
||||||
|
user.CreateBy = createBy
|
||||||
|
|
||||||
|
id, err := SysSequenceServiceBuilder(user.TableName()).GenerateId()
|
||||||
|
if err != nil {
|
||||||
|
return serializer.DBErr("创建用户失败!", err)
|
||||||
|
}
|
||||||
|
user.UserId = id
|
||||||
|
|
||||||
|
if err := s.Db.Create(&user).Error; err != nil {
|
||||||
|
logger.Error(c, "创建用户失败!")
|
||||||
|
return serializer.DBErr("创建用户失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("创建用户成功!", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID 根据ID删除用户
|
||||||
|
func (s *SysUserService) DeleteByID(c *gin.Context) serializer.Response {
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
logger.Error(c, "id 不可为空!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除逻辑
|
||||||
|
data := map[string]any{
|
||||||
|
"del_flag": "1",
|
||||||
|
"update_time": time.Now(),
|
||||||
|
"update_by": c.GetString("id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var user model.SysUser
|
||||||
|
if err := s.Db.Model(&user).Where("user_id = ?", id).Updates(data).Error; err != nil {
|
||||||
|
logger.Error(c, "未查询用户信息!")
|
||||||
|
return serializer.DBErr("删除用户失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("删除用户成功!", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateByID 根据ID更新用户
|
||||||
|
func (s *SysUserService) UpdateByID(c *gin.Context) serializer.Response {
|
||||||
|
var user model.SysUser
|
||||||
|
if err := c.ShouldBind(&user); err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := user.UserId
|
||||||
|
if id == "" {
|
||||||
|
logger.Error(c, "id 不可为空!")
|
||||||
|
return serializer.ParamErr("id不可为空!", fmt.Errorf("id不可为空"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Model(&user).Where("user_id = ?", id).Updates(&user).Error; err != nil {
|
||||||
|
logger.Error(c, "更新用户信息失败!")
|
||||||
|
return serializer.DBErr("更新用户信息失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("更新用户信息成功!", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID获取用户
|
||||||
|
func (s *SysUserService) GetByID(c *gin.Context) serializer.Response {
|
||||||
|
var user model.SysUser
|
||||||
|
if err := s.Db.Where("user_id = ?", c.Param("id")).First(&user).Error; err != nil {
|
||||||
|
logger.Error(c, "获取用户信息失败!")
|
||||||
|
return serializer.DBErr("获取用户信息失败!", err)
|
||||||
|
}
|
||||||
|
user.PassWord = "" // 清除密码
|
||||||
|
return serializer.Succ("查询成功!", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByIDs 批量删除用户
|
||||||
|
func (s *SysUserService) DeleteByIDs(c *gin.Context) serializer.Response {
|
||||||
|
var ids types.Payload
|
||||||
|
if err := c.ShouldBind(&ids); err != nil {
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除逻辑
|
||||||
|
data := map[string]any{
|
||||||
|
"del_flag": "1",
|
||||||
|
"update_time": time.Now(),
|
||||||
|
"update_by": c.GetString("id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Db.Model(&model.SysUser{}).Where("user_id in (?)", ids.Ids).Updates(data).Error; err != nil {
|
||||||
|
logger.Error(c, "批量删除用户失败!")
|
||||||
|
return serializer.DBErr("批量删除用户失败!", err)
|
||||||
|
}
|
||||||
|
return serializer.Succ("批量删除用户成功!", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCondition 条件查询用户
|
||||||
|
func (s *SysUserService) GetByCondition(c *gin.Context) serializer.Response {
|
||||||
|
var p types.Params
|
||||||
|
if err := c.ShouldBind(&p); err != nil {
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryStr, args, err := p.ConvertToGormConditions()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(c, "参数绑定失败!")
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
var users []model.SysUser
|
||||||
|
offset := (p.Page - 1) * p.Limit
|
||||||
|
|
||||||
|
// 构建基础查询
|
||||||
|
db := s.Db.Model(&model.SysUser{})
|
||||||
|
|
||||||
|
// 如果有查询条件,添加条件
|
||||||
|
if queryStr != "" {
|
||||||
|
db = db.Where(queryStr, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
if p.Sort != "" {
|
||||||
|
db = db.Order(p.Sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
if err := db.Where("del_flag = ?", "0").Offset(offset).Limit(p.Limit).Find(&users).Error; err != nil {
|
||||||
|
logger.Error(c, "获取用户信息失败!")
|
||||||
|
return serializer.DBErr("获取用户信息失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有用户的密码
|
||||||
|
for i := range users {
|
||||||
|
users[i].PassWord = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行总数查询
|
||||||
|
if err := db.Where("del_flag = ?", "0").Count(&total).Error; err != nil {
|
||||||
|
logger.Error(c, "获取用户总数失败!")
|
||||||
|
return serializer.DBErr("获取用户总数失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("查询成功!", gin.H{
|
||||||
|
"total": total,
|
||||||
|
"items": users,
|
||||||
|
"page": p.Page,
|
||||||
|
"limit": p.Limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByIDs 根据ID列表获取用户
|
||||||
|
func (s *SysUserService) ListByIDs(c *gin.Context) serializer.Response {
|
||||||
|
var ids types.Payload
|
||||||
|
if err := c.ShouldBind(&ids); err != nil {
|
||||||
|
return serializer.ParamErr("参数绑定失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []model.SysUser
|
||||||
|
if err := s.Db.Where("user_id in (?)", ids.Ids).Find(&users).Error; err != nil {
|
||||||
|
logger.Error(c, "获取用户信息失败!")
|
||||||
|
return serializer.DBErr("获取用户信息失败!", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有用户的密码
|
||||||
|
for i := range users {
|
||||||
|
users[i].PassWord = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializer.Succ("查询成功!", users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentUser 获取当前用户
|
||||||
|
func (s *SysUserService) CurrentUser(c *gin.Context) (*model.SysUser, error) {
|
||||||
|
tokenString := c.GetHeader("Authorization")
|
||||||
|
var user model.SysUser
|
||||||
|
token, err := util.ParseToken(tokenString[8:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取当前用户失败! %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := token["id"].(string)
|
||||||
|
if err := s.Db.Model(&user).Where("user_id = ?", id).First(&user).Error; err != nil {
|
||||||
|
logger.Error(c, "获取当前用户失败!", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 清除密码
|
||||||
|
user.PassWord = ""
|
||||||
|
return &user, nil
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
var defaultMaxSize = 1000
|
||||||
|
|
||||||
|
// SetMaxSize change the default maximum number of pages per page
|
||||||
|
func SetMaxSize(max int) {
|
||||||
|
if max < 10 {
|
||||||
|
max = 10
|
||||||
|
}
|
||||||
|
defaultMaxSize = max
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page info
|
||||||
|
type Page struct {
|
||||||
|
page int // page number, starting from page 0
|
||||||
|
limit int // number per page
|
||||||
|
sort string // sort fields, default is id backwards, you can add - sign before the field to indicate reverse order, no - sign to indicate ascending order, multiple fields separated by comma
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page get page value
|
||||||
|
func (p *Page) Page() int {
|
||||||
|
return p.page
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit number per page
|
||||||
|
func (p *Page) Limit() int {
|
||||||
|
return p.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size number per page
|
||||||
|
// Deprecated: use Limit instead
|
||||||
|
func (p *Page) Size() int {
|
||||||
|
return p.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort get sort field
|
||||||
|
func (p *Page) Sort() string {
|
||||||
|
return p.sort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset get offset value
|
||||||
|
func (p *Page) Offset() int {
|
||||||
|
return p.page * p.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPage default page, number 20 per page, sorted by id backwards
|
||||||
|
func DefaultPage(page int) *Page {
|
||||||
|
if page < 0 {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
return &Page{
|
||||||
|
page: page,
|
||||||
|
limit: 20,
|
||||||
|
sort: "id DESC",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPage custom page, starting from page 0.
|
||||||
|
// the parameter columnNames indicates a sort field, if empty means id descending,
|
||||||
|
// if there are multiple column names, separated by a comma,
|
||||||
|
// a '-' sign in front of each column name indicates descending order, otherwise ascending order.
|
||||||
|
func NewPage(page int, limit int, columnNames string) *Page {
|
||||||
|
if page < 0 {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
if limit > defaultMaxSize || limit < 1 {
|
||||||
|
limit = defaultMaxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Page{
|
||||||
|
page: page,
|
||||||
|
limit: limit,
|
||||||
|
sort: getSort(columnNames),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to mysql sort, each column name preceded by a '-' sign, indicating descending order, otherwise ascending order, example:
|
||||||
|
//
|
||||||
|
// columnNames="name" means sort by name in ascending order,
|
||||||
|
// columnNames="-name" means sort by name descending,
|
||||||
|
// columnNames="name,age" means sort by name in ascending order, otherwise sort by age in ascending order,
|
||||||
|
// columnNames="-name,-age" means sort by name descending before sorting by age descending.
|
||||||
|
func getSort(columnNames string) string {
|
||||||
|
columnNames = strings.Replace(columnNames, " ", "", -1)
|
||||||
|
if columnNames == "" {
|
||||||
|
return "id DESC"
|
||||||
|
}
|
||||||
|
names := strings.Split(columnNames, ",")
|
||||||
|
strs := make([]string, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
if name[0] == '-' && len(name) > 1 {
|
||||||
|
strs = append(strs, name[1:]+" DESC")
|
||||||
|
} else {
|
||||||
|
strs = append(strs, name+" ASC")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(strs, ", ")
|
||||||
|
}
|
|
@ -0,0 +1,525 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Eq 等于
|
||||||
|
Eq = "eq"
|
||||||
|
// Neq 不等于
|
||||||
|
Neq = "neq"
|
||||||
|
// Gt 大于
|
||||||
|
Gt = "gt"
|
||||||
|
// Gte 大于等于
|
||||||
|
Gte = "gte"
|
||||||
|
// Lt 小于
|
||||||
|
Lt = "lt"
|
||||||
|
// Lte 小于等于
|
||||||
|
Lte = "lte"
|
||||||
|
// Like 模糊查询
|
||||||
|
Like = "like"
|
||||||
|
// In 包含
|
||||||
|
In = "in"
|
||||||
|
// AND 逻辑与
|
||||||
|
AND string = "and"
|
||||||
|
// OR 逻辑或
|
||||||
|
OR string = "or"
|
||||||
|
)
|
||||||
|
|
||||||
|
// expMap 表达式映射表,将查询条件转换为SQL表达式
|
||||||
|
var expMap = map[string]string{
|
||||||
|
Eq: " = ",
|
||||||
|
Neq: " <> ",
|
||||||
|
Gt: " > ",
|
||||||
|
Gte: " >= ",
|
||||||
|
Lt: " < ",
|
||||||
|
Lte: " <= ",
|
||||||
|
Like: " LIKE ",
|
||||||
|
In: " IN ",
|
||||||
|
|
||||||
|
"=": " = ",
|
||||||
|
"!=": " <> ",
|
||||||
|
">": " > ",
|
||||||
|
">=": " >= ",
|
||||||
|
"<": " < ",
|
||||||
|
"<=": " <= ",
|
||||||
|
}
|
||||||
|
|
||||||
|
// logicMap 逻辑运算符映射表,将逻辑运算符转换为SQL逻辑运算符
|
||||||
|
var logicMap = map[string]string{
|
||||||
|
AND: " AND ",
|
||||||
|
OR: " OR ",
|
||||||
|
|
||||||
|
"&": " AND ",
|
||||||
|
"&&": " AND ",
|
||||||
|
"|": " OR ",
|
||||||
|
"||": " OR ",
|
||||||
|
"AND": " AND ",
|
||||||
|
"OR": " OR ",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params 查询参数结构体
|
||||||
|
type Params struct {
|
||||||
|
Page int `json:"page" form:"page" binding:"gte=0"` // 页码,从0开始
|
||||||
|
Limit int `json:"limit" form:"limit" binding:"gte=10"` // 每页数量
|
||||||
|
Sort string `json:"sort,omitempty" form:"sort" binding:""` // 排序字段
|
||||||
|
Columns []Column `json:"columns,omitempty" form:"columns"` // 查询条件列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column 查询条件结构体
|
||||||
|
type Column struct {
|
||||||
|
Name string `json:"name" form:"name"` // 字段名
|
||||||
|
Exp string `json:"exp" form:"exp"` // 表达式类型,默认为等于(=),支持 =, !=, >, >=, <, <=, like, in
|
||||||
|
Value any `json:"value" form:"value"` // 字段值
|
||||||
|
Logic string `json:"logic" form:"logic"` // 逻辑运算符,默认为and,支持 &(and), ||(or)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkValid 检查查询条件是否有效
|
||||||
|
func (c *Column) checkValid() error {
|
||||||
|
if c.Name == "" {
|
||||||
|
return fmt.Errorf("字段名不能为空")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert 将表达式类型和逻辑运算符转换为SQL语句
|
||||||
|
func (c *Column) convert() error {
|
||||||
|
if c.Exp == "" {
|
||||||
|
c.Exp = Eq
|
||||||
|
}
|
||||||
|
if v, ok := expMap[strings.ToLower(c.Exp)]; ok { //nolint
|
||||||
|
c.Exp = v
|
||||||
|
if c.Exp == " LIKE " {
|
||||||
|
c.Value = fmt.Sprintf("%%%v%%", c.Value)
|
||||||
|
}
|
||||||
|
if c.Exp == " IN " {
|
||||||
|
val, ok := c.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("IN查询的值类型无效: '%s'", c.Value)
|
||||||
|
}
|
||||||
|
var iVal []any
|
||||||
|
ss := strings.Split(val, ",")
|
||||||
|
for _, s := range ss {
|
||||||
|
iVal = append(iVal, s)
|
||||||
|
}
|
||||||
|
c.Value = iVal
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("未知的表达式类型: '%s'", c.Exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Logic == "" {
|
||||||
|
c.Logic = AND
|
||||||
|
}
|
||||||
|
if v, ok := logicMap[strings.ToLower(c.Logic)]; ok { //nolint
|
||||||
|
c.Logic = v
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("未知的逻辑运算符类型: '%s'", c.Logic)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToPage 转换为分页参数
|
||||||
|
func (p *Params) ConvertToPage() (order string, limit int, offset int) { //nolint
|
||||||
|
page := NewPage(p.Page, p.Limit, p.Sort)
|
||||||
|
order = page.sort
|
||||||
|
limit = page.limit
|
||||||
|
offset = page.page * page.limit
|
||||||
|
return //nolint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToGormConditions 将查询条件转换为GORM兼容的参数
|
||||||
|
// 忽略最后一个条件的逻辑运算符,无论是单条件还是多条件查询
|
||||||
|
func (p *Params) ConvertToGormConditions() (string, []any, error) {
|
||||||
|
str := ""
|
||||||
|
var args []any
|
||||||
|
var validColumns []Column
|
||||||
|
|
||||||
|
// 过滤掉空值参数
|
||||||
|
for _, column := range p.Columns {
|
||||||
|
if column.Value != nil && column.Value != "" {
|
||||||
|
validColumns = append(validColumns, column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l := len(validColumns)
|
||||||
|
if l == 0 {
|
||||||
|
return "", nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isUseIN := true
|
||||||
|
if l == 1 {
|
||||||
|
isUseIN = false
|
||||||
|
}
|
||||||
|
field := validColumns[0].Name
|
||||||
|
|
||||||
|
for i, column := range validColumns {
|
||||||
|
if err := column.checkValid(); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := column.convert()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol := "?"
|
||||||
|
if column.Exp == " IN " {
|
||||||
|
symbol = "(?)"
|
||||||
|
}
|
||||||
|
if i == l-1 { // 忽略最后一个条件的逻辑运算符
|
||||||
|
str += column.Name + column.Exp + symbol
|
||||||
|
} else {
|
||||||
|
str += column.Name + column.Exp + symbol + column.Logic
|
||||||
|
}
|
||||||
|
args = append(args, column.Value)
|
||||||
|
|
||||||
|
// 当多个条件字段相同时,判断是否使用IN查询
|
||||||
|
if isUseIN {
|
||||||
|
if field != column.Name {
|
||||||
|
isUseIN = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if column.Exp != expMap[Eq] {
|
||||||
|
isUseIN = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isUseIN {
|
||||||
|
str = field + " IN (?)"
|
||||||
|
args = []any{args}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str, args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conditions 查询条件结构体
|
||||||
|
type Conditions struct {
|
||||||
|
Columns []Column `json:"columns" form:"columns" binding:"min=1"` // 查询条件列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckValid 检查查询条件是否有效
|
||||||
|
func (c *Conditions) CheckValid() error {
|
||||||
|
if len(c.Columns) == 0 {
|
||||||
|
return fmt.Errorf("查询条件不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, column := range c.Columns {
|
||||||
|
err := column.checkValid()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if column.Exp != "" {
|
||||||
|
if _, ok := expMap[column.Exp]; !ok {
|
||||||
|
return fmt.Errorf("未知的表达式类型: '%s'", column.Exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if column.Logic != "" {
|
||||||
|
if _, ok := logicMap[column.Logic]; !ok {
|
||||||
|
return fmt.Errorf("未知的逻辑运算符类型: '%s'", column.Logic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToGorm 将查询条件转换为GORM兼容的参数
|
||||||
|
// 忽略最后一个条件的逻辑运算符,无论是单条件还是多条件查询
|
||||||
|
func (c *Conditions) ConvertToGorm() (string, []any, error) {
|
||||||
|
p := &Params{Columns: c.Columns}
|
||||||
|
return p.ConvertToGormConditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload 通用负载结构体
|
||||||
|
type Payload struct {
|
||||||
|
Ids []string `json:"ids"` // ID列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryBuilder 查询构造器,提供链式调用方式
|
||||||
|
type QueryBuilder struct {
|
||||||
|
params *Params
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryBuilder 创建查询构造器
|
||||||
|
func NewQueryBuilder() *QueryBuilder {
|
||||||
|
return &QueryBuilder{
|
||||||
|
params: &Params{
|
||||||
|
Page: 1,
|
||||||
|
Limit: 10,
|
||||||
|
Columns: make([]Column, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page 设置页码
|
||||||
|
func (qb *QueryBuilder) Page(page int) *QueryBuilder {
|
||||||
|
qb.params.Page = page
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit 设置每页数量
|
||||||
|
func (qb *QueryBuilder) Limit(limit int) *QueryBuilder {
|
||||||
|
qb.params.Limit = limit
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort 设置排序
|
||||||
|
func (qb *QueryBuilder) Sort(sort string) *QueryBuilder {
|
||||||
|
qb.params.Sort = sort
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eq 等于条件
|
||||||
|
func (qb *QueryBuilder) Eq(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Eq,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like 模糊查询条件
|
||||||
|
func (qb *QueryBuilder) Like(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Like,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// In 包含条件
|
||||||
|
func (qb *QueryBuilder) In(name string, values ...any) *QueryBuilder {
|
||||||
|
var valueStr []string
|
||||||
|
for _, v := range values {
|
||||||
|
valueStr = append(valueStr, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: In,
|
||||||
|
Value: strings.Join(valueStr, ","),
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gt 大于条件
|
||||||
|
func (qb *QueryBuilder) Gt(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Gt,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gte 大于等于条件
|
||||||
|
func (qb *QueryBuilder) Gte(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Gte,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lt 小于条件
|
||||||
|
func (qb *QueryBuilder) Lt(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Lt,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lte 小于等于条件
|
||||||
|
func (qb *QueryBuilder) Lte(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Lte,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neq 不等于条件
|
||||||
|
func (qb *QueryBuilder) Neq(name string, value any) *QueryBuilder {
|
||||||
|
qb.params.Columns = append(qb.params.Columns, Column{
|
||||||
|
Name: name,
|
||||||
|
Exp: Neq,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or 设置最后一个条件为OR逻辑
|
||||||
|
func (qb *QueryBuilder) Or() *QueryBuilder {
|
||||||
|
if len(qb.params.Columns) > 0 {
|
||||||
|
qb.params.Columns[len(qb.params.Columns)-1].Logic = OR
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build 构建查询参数
|
||||||
|
func (qb *QueryBuilder) Build() *Params {
|
||||||
|
return qb.params
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToGormConditions 转换为GORM条件
|
||||||
|
func (qb *QueryBuilder) ToGormConditions() (string, []any, error) {
|
||||||
|
return qb.params.ConvertToGormConditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLQuery URL查询参数结构,支持通用的URL参数解析
|
||||||
|
type URLQuery struct {
|
||||||
|
Page int `json:"page" form:"page" binding:"gte=0"`
|
||||||
|
Limit int `json:"limit" form:"limit" binding:"gte=10"`
|
||||||
|
Sort string `json:"sort,omitempty" form:"sort"`
|
||||||
|
Query string `json:"query,omitempty" form:"query"` // 通用查询字符串,格式:field1=value1&field2__like=value2
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseQuery 解析查询字符串为Params
|
||||||
|
func (uq *URLQuery) ParseQuery() (*Params, error) {
|
||||||
|
params := &Params{
|
||||||
|
Page: uq.Page,
|
||||||
|
Limit: uq.Limit,
|
||||||
|
Sort: uq.Sort,
|
||||||
|
Columns: make([]Column, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if uq.Query == "" {
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的查询字符串解析,支持 field=value 和 field__exp=value 格式
|
||||||
|
pairs := strings.Split(uq.Query, "&")
|
||||||
|
for _, pair := range pairs {
|
||||||
|
kv := strings.Split(pair, "=")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(kv[0])
|
||||||
|
value := strings.TrimSpace(kv[1])
|
||||||
|
|
||||||
|
if key == "" || value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持特殊语法:字段名__操作符
|
||||||
|
parts := strings.Split(key, "__")
|
||||||
|
fieldName := parts[0]
|
||||||
|
exp := Eq // 默认等于
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
switch parts[1] {
|
||||||
|
case "like":
|
||||||
|
exp = Like
|
||||||
|
case "gt":
|
||||||
|
exp = Gt
|
||||||
|
case "gte":
|
||||||
|
exp = Gte
|
||||||
|
case "lt":
|
||||||
|
exp = Lt
|
||||||
|
case "lte":
|
||||||
|
exp = Lte
|
||||||
|
case "neq":
|
||||||
|
exp = Neq
|
||||||
|
case "in":
|
||||||
|
exp = In
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Columns = append(params.Columns, Column{
|
||||||
|
Name: fieldName,
|
||||||
|
Exp: exp,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToGormConditions 转换为GORM条件
|
||||||
|
func (uq *URLQuery) ToGormConditions() (string, []any, error) {
|
||||||
|
params, err := uq.ParseQuery()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return params.ConvertToGormConditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyValueQuery 键值对查询结构,支持动态字段查询
|
||||||
|
type KeyValueQuery struct {
|
||||||
|
Page int `json:"page" form:"page" binding:"gte=0"`
|
||||||
|
Limit int `json:"limit" form:"limit" binding:"gte=10"`
|
||||||
|
Sort string `json:"sort,omitempty" form:"sort"`
|
||||||
|
Conditions map[string]interface{} `json:"conditions,omitempty" form:"conditions"` // 动态查询条件
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToParams 转换为标准Params结构
|
||||||
|
func (kv *KeyValueQuery) ToParams() *Params {
|
||||||
|
params := &Params{
|
||||||
|
Page: kv.Page,
|
||||||
|
Limit: kv.Limit,
|
||||||
|
Sort: kv.Sort,
|
||||||
|
Columns: make([]Column, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range kv.Conditions {
|
||||||
|
if value != nil && value != "" {
|
||||||
|
// 支持特殊语法:字段名__操作符
|
||||||
|
parts := strings.Split(key, "__")
|
||||||
|
fieldName := parts[0]
|
||||||
|
exp := Eq // 默认等于
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
switch parts[1] {
|
||||||
|
case "like":
|
||||||
|
exp = Like
|
||||||
|
case "gt":
|
||||||
|
exp = Gt
|
||||||
|
case "gte":
|
||||||
|
exp = Gte
|
||||||
|
case "lt":
|
||||||
|
exp = Lt
|
||||||
|
case "lte":
|
||||||
|
exp = Lte
|
||||||
|
case "neq":
|
||||||
|
exp = Neq
|
||||||
|
case "in":
|
||||||
|
exp = In
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Columns = append(params.Columns, Column{
|
||||||
|
Name: fieldName,
|
||||||
|
Exp: exp,
|
||||||
|
Value: value,
|
||||||
|
Logic: AND,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToGormConditions 转换为GORM条件
|
||||||
|
func (kv *KeyValueQuery) ToGormConditions() (string, []any, error) {
|
||||||
|
return kv.ToParams().ConvertToGormConditions()
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/model"
|
||||||
|
"ego/internal/serializer"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// User 用户序列化器
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserName string `json:"userName"`
|
||||||
|
Nickname string `json:"nickName"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
CreatedAt int64 `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildUser 序列化用户
|
||||||
|
func BuildUser(user model.SysUser) User {
|
||||||
|
return User{
|
||||||
|
ID: user.UserId,
|
||||||
|
UserName: user.UserName,
|
||||||
|
Nickname: user.NickName,
|
||||||
|
Status: user.Status,
|
||||||
|
Avatar: user.Avatar,
|
||||||
|
CreatedAt: user.CreateTime.Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildUserHasToken BuildUser 序列化用户
|
||||||
|
func BuildUserHasToken(user model.SysUser, token string) User {
|
||||||
|
return User{
|
||||||
|
ID: user.UserId,
|
||||||
|
UserName: user.UserName,
|
||||||
|
Nickname: user.NickName,
|
||||||
|
Status: user.Status,
|
||||||
|
Avatar: user.Avatar,
|
||||||
|
CreatedAt: user.CreateTime.Unix(),
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildUserResponse 序列化用户响应
|
||||||
|
func BuildUserResponse(user model.SysUser) serializer.Response {
|
||||||
|
return serializer.Response{Code: http.StatusOK, Data: BuildUser(user)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildUserResponseHasToken BuildUserResponse 序列化用户响应
|
||||||
|
func BuildUserResponseHasToken(user model.SysUser, token string) serializer.Response {
|
||||||
|
return serializer.Response{Code: http.StatusOK, Msg: "登录成功!", Data: BuildUserHasToken(user, token)}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandStringRunes 返回随机字符串
|
||||||
|
func RandStringRunes(n int) string {
|
||||||
|
var letterRunes = []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
rand.NewSource(time.Now().UnixNano())
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateID 生成32位唯一ID
|
||||||
|
func GenerateID() string {
|
||||||
|
return strings.ToLower(RandStringRunes(32))
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenExpireTime = 24 * time.Hour // Token 过期时间(24小时)
|
||||||
|
// TokenGroup token
|
||||||
|
TokenGroup = "user_token:"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// JwtKey JWT签名密钥
|
||||||
|
JwtKey []byte
|
||||||
|
jwtKeyOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claims JWT声明结构
|
||||||
|
type Claims struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
// initJwtKey 懒加载方式初始化JWT密钥
|
||||||
|
func initJwtKey() {
|
||||||
|
jwtKeyOnce.Do(func() {
|
||||||
|
secret := os.Getenv("JWT_SECRET")
|
||||||
|
if secret == "" {
|
||||||
|
secret = "default-jwt-secret-please-change-in-production"
|
||||||
|
// 注意:这里不能使用logger,因为可能还没初始化
|
||||||
|
// 如果需要日志,在配置初始化完成后再输出
|
||||||
|
}
|
||||||
|
JwtKey = []byte(secret)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getJwtKey 获取JWT密钥,确保已初始化
|
||||||
|
func getJwtKey() []byte {
|
||||||
|
initJwtKey()
|
||||||
|
return JwtKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateToken 生成JWT Token
|
||||||
|
func GenerateToken(id, username string) (string, error) {
|
||||||
|
if id == "" || username == "" {
|
||||||
|
return "", fmt.Errorf("用户ID和用户名不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := getJwtKey()
|
||||||
|
expireTime := time.Now().Add(TokenExpireTime)
|
||||||
|
|
||||||
|
claims := &Claims{
|
||||||
|
ID: id,
|
||||||
|
Username: username,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expireTime),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "ego-system",
|
||||||
|
Subject: username,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(key)
|
||||||
|
if err != nil {
|
||||||
|
// 安全地记录错误,如果logger已初始化
|
||||||
|
if logger.Logger != nil {
|
||||||
|
logger.Error(nil, "JWT Token生成失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("token生成失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseToken 解析JWT Token
|
||||||
|
func ParseToken(tokenString string) (jwt.MapClaims, error) {
|
||||||
|
if tokenString == "" {
|
||||||
|
return nil, fmt.Errorf("token不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := getJwtKey()
|
||||||
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("无效的签名方法: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 安全地记录错误,如果logger已初始化
|
||||||
|
if logger.Logger != nil {
|
||||||
|
logger.Error(nil, "JWT Token解析失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("token解析失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.Valid {
|
||||||
|
return nil, fmt.Errorf("token无效或已过期")
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("token claims格式错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateToken 验证Token有效性
|
||||||
|
func ValidateToken(tokenString string) (*Claims, error) {
|
||||||
|
if tokenString == "" {
|
||||||
|
return nil, fmt.Errorf("token不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := getJwtKey()
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (any, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("无效的签名方法: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 安全地记录错误,如果logger已初始化
|
||||||
|
if logger.Logger != nil {
|
||||||
|
logger.Error(nil, "JWT Token验证失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("token验证失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("token无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogJwtKeyStatus 在配置初始化完成后输出JWT密钥状态日志
|
||||||
|
func LogJwtKeyStatus() {
|
||||||
|
secret := os.Getenv("JWT_SECRET")
|
||||||
|
if secret == "" {
|
||||||
|
logger.Warn(nil, "使用默认JWT密钥,生产环境请设置JWT_SECRET环境变量")
|
||||||
|
} else {
|
||||||
|
logger.Info(nil, "JWT密钥已从环境变量加载")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"ego/internal/cache"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set 设置键值对到Redis
|
||||||
|
func Set(ctx *gin.Context, key string, value any, expiration time.Duration) error {
|
||||||
|
err := cache.RedisClient.Set(ctx, key, value, expiration).Err()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "Redis Set 失败", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 从Redis获取键值对
|
||||||
|
func Get(ctx *gin.Context, key string) (string, error) {
|
||||||
|
val, err := cache.RedisClient.Get(ctx, key).Result()
|
||||||
|
if err == redis.Nil {
|
||||||
|
logger.Info(ctx, "Redis key 不存在", zap.String("key", key))
|
||||||
|
return "", nil
|
||||||
|
} else if err != nil {
|
||||||
|
logger.Error(ctx, "Redis Get 失败", zap.Error(err))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Del 从Redis删除键
|
||||||
|
func Del(ctx *gin.Context, key string) error {
|
||||||
|
err := cache.RedisClient.Del(ctx, key).Err()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "Redis Del 失败", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists 检查Redis中是否存在键
|
||||||
|
func Exists(ctx *gin.Context, key string) (bool, error) {
|
||||||
|
val, err := cache.RedisClient.Exists(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "Redis Exists 失败", zap.Error(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return val > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr 对Redis中的键进行自增操作
|
||||||
|
func Incr(ctx *gin.Context, key string) (int64, error) {
|
||||||
|
val, err := cache.RedisClient.Incr(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "Redis Incr 失败", zap.Error(err))
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr 对Redis中的键进行自减操作
|
||||||
|
func Decr(ctx *gin.Context, key string) (int64, error) {
|
||||||
|
val, err := cache.RedisClient.Decr(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "Redis Decr 失败", zap.Error(err))
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// TrimSpaces 去除字符串两端的空格
|
||||||
|
func TrimSpaces(s string) string {
|
||||||
|
return strings.ReplaceAll(s, " ", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStringEmpty 判断字符串是否为空
|
||||||
|
func IsStringEmpty(s string) bool {
|
||||||
|
return TrimSpaces(s) == ""
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseUserAgent 解析User-Agent字符串,返回操作系统信息
|
||||||
|
func ParseUserAgent(userAgent string) string {
|
||||||
|
if userAgent == "" {
|
||||||
|
return "未知"
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgent = strings.ToLower(userAgent)
|
||||||
|
|
||||||
|
// 操作系统判断
|
||||||
|
switch {
|
||||||
|
case strings.Contains(userAgent, "windows"):
|
||||||
|
return "Windows"
|
||||||
|
case strings.Contains(userAgent, "macintosh") || strings.Contains(userAgent, "mac os x"):
|
||||||
|
return "MacOS"
|
||||||
|
case strings.Contains(userAgent, "linux"):
|
||||||
|
return "Linux"
|
||||||
|
case strings.Contains(userAgent, "android"):
|
||||||
|
return "Android"
|
||||||
|
case strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipad") || strings.Contains(userAgent, "ipod"):
|
||||||
|
return "iOS"
|
||||||
|
case strings.Contains(userAgent, "freebsd"):
|
||||||
|
return "FreeBSD"
|
||||||
|
case strings.Contains(userAgent, "openbsd"):
|
||||||
|
return "OpenBSD"
|
||||||
|
case strings.Contains(userAgent, "netbsd"):
|
||||||
|
return "NetBSD"
|
||||||
|
case strings.Contains(userAgent, "sunos"):
|
||||||
|
return "Solaris"
|
||||||
|
default:
|
||||||
|
return "未知"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
//go:build wireinject
|
||||||
|
// +build wireinject
|
||||||
|
|
||||||
|
// The build tag makes sure the stub is not built in the final build.
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ego/internal/conf"
|
||||||
|
"ego/internal/handler"
|
||||||
|
"ego/internal/service"
|
||||||
|
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DBSet 提供数据库连接
|
||||||
|
var DBSet = wire.NewSet(
|
||||||
|
conf.NewDb, // 依赖 conf 包的 NewDb
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerSet 处理器集合
|
||||||
|
var HandlerSet = wire.NewSet(
|
||||||
|
handler.NewSysUserHandler,
|
||||||
|
handler.NewSysLoginLogHandler,
|
||||||
|
handler.NewSysUploadHandler,
|
||||||
|
handler.NewSysDeployFileHandler,
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserServiceSet 定义 service 层依赖
|
||||||
|
var UserServiceSet = wire.NewSet(
|
||||||
|
service.NewSysUserService,
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoginLogServiceSet 服务器信息服务层依赖
|
||||||
|
var LoginLogServiceSet = wire.NewSet(
|
||||||
|
service.NewSysLoginLogService,
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadServiceSet 文件上传服务层依赖
|
||||||
|
var UploadServiceSet = wire.NewSet(
|
||||||
|
service.NewSysUploadService,
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeployFileServiceSet 部署文件服务层依赖
|
||||||
|
var DeployFileServiceSet = wire.NewSet(
|
||||||
|
service.NewSysDeployFileService,
|
||||||
|
)
|
||||||
|
|
||||||
|
// InjectSysUserHandler 注入 handler
|
||||||
|
func InjectSysUserHandler() *handler.SysUserHandler {
|
||||||
|
panic(wire.Build(
|
||||||
|
HandlerSet,
|
||||||
|
UserServiceSet,
|
||||||
|
DBSet,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectSysLoginLogHandler 注入登录记录信息处理器
|
||||||
|
func InjectSysLoginLogHandler() *handler.SysLoginLogHandler {
|
||||||
|
panic(wire.Build(HandlerSet, LoginLogServiceSet, DBSet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectSysUploadHandler 注入文件上传处理器
|
||||||
|
func InjectSysUploadHandler() *handler.SysUploadHandler {
|
||||||
|
panic(wire.Build(HandlerSet, UploadServiceSet, DBSet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectSysDeployFileHandler 注入部署文件处理器
|
||||||
|
func InjectSysDeployFileHandler() *handler.SysDeployFileHandler {
|
||||||
|
panic(wire.Build(HandlerSet, DeployFileServiceSet, DBSet))
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.697","caller":"sync/once.go:76","msg":"日志系统初始化成功","log_path":"./logs/","log_level":"info"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.792","caller":"conf/db.go:92","msg":"数据库连接成功","connString":"***已遮盖敏感信息***","maxOpenConns":20,"maxIdleConns":10}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.871","caller":"cache/cache.go:76","msg":"Redis连接成功","address":"www.suyun.store:6379","db":0,"pool_size":20,"read_timeout":3}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.871","caller":"util/jwt_utils.go:155","msg":"JWT密钥已从环境变量加载"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.871","caller":"conf/conf.go:57","msg":"所有配置初始化完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.872","caller":"ego/main.go:44","msg":"Gin模式设置完成","mode":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.872","caller":"middleware/cors.go:63","msg":"跨域配置初始化完成","允许方法":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"允许头":["Origin","Content-Length","Content-Type","Cookie","Authorization","X-Requested-With","X-CSRF-Token"],"允许来源":[],"MaxAge":43200,"模式":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.873","caller":"ego/main.go:70","msg":"服务器启动成功","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:37.873","caller":"ego/main.go:63","msg":"服务器启动中...","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"ego/main.go:77","msg":"收到关闭信号,开始优雅关闭服务器..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"ego/main.go:87","msg":"HTTP服务器已优雅关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"conf/conf.go:63","msg":"开始关闭应用程序资源..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"conf/conf.go:69","msg":"数据库已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"cache/cache.go:93","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"conf/conf.go:76","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"conf/conf.go:79","msg":"应用程序资源关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:54:44.112","caller":"ego/main.go:93","msg":"服务器关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:18.971","caller":"sync/once.go:76","msg":"日志系统初始化成功","log_path":"./logs/","log_level":"info"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.104","caller":"conf/db.go:92","msg":"数据库连接成功","connString":"***已遮盖敏感信息***","maxOpenConns":20,"maxIdleConns":10}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"cache/cache.go:76","msg":"Redis连接成功","address":"www.suyun.store:6379","db":0,"pool_size":20,"read_timeout":3}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"util/jwt_utils.go:155","msg":"JWT密钥已从环境变量加载"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"conf/conf.go:57","msg":"所有配置初始化完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"ego/main.go:44","msg":"Gin模式设置完成","mode":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"middleware/cors.go:63","msg":"跨域配置初始化完成","允许方法":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"允许头":["Origin","Content-Length","Content-Type","Cookie","Authorization","X-Requested-With","X-CSRF-Token"],"允许来源":[],"MaxAge":43200,"模式":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"ego/main.go:70","msg":"服务器启动成功","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:19.157","caller":"ego/main.go:63","msg":"服务器启动中...","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"ego/main.go:77","msg":"收到关闭信号,开始优雅关闭服务器..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"ego/main.go:87","msg":"HTTP服务器已优雅关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"conf/conf.go:63","msg":"开始关闭应用程序资源..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"conf/conf.go:69","msg":"数据库已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"cache/cache.go:93","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"conf/conf.go:76","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.145","caller":"conf/conf.go:79","msg":"应用程序资源关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 13:58:21.146","caller":"ego/main.go:93","msg":"服务器关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.26","caller":"sync/once.go:76","msg":"日志系统初始化成功","log_path":"./logs/","log_level":"info"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.401","caller":"conf/db.go:92","msg":"数据库连接成功","connString":"***已遮盖敏感信息***","maxOpenConns":20,"maxIdleConns":10}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.464","caller":"cache/cache.go:76","msg":"Redis连接成功","address":"www.suyun.store:6379","db":0,"pool_size":20,"read_timeout":3}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.464","caller":"util/jwt_utils.go:155","msg":"JWT密钥已从环境变量加载"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.464","caller":"conf/conf.go:57","msg":"所有配置初始化完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.464","caller":"ego/main.go:44","msg":"Gin模式设置完成","mode":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.464","caller":"middleware/cors.go:63","msg":"跨域配置初始化完成","允许方法":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"允许头":["Origin","Content-Length","Content-Type","Cookie","Authorization","X-Requested-With","X-CSRF-Token"],"允许来源":[],"MaxAge":43200,"模式":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.465","caller":"ego/main.go:70","msg":"服务器启动成功","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:18:56.465","caller":"ego/main.go:63","msg":"服务器启动中...","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:21:00.48","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"60125fcd-186e-402e-b8cc-e0a56984cf7a","method":"POST","path":"/:3000127.0.0.1/upload/zip","status":404,"duration":0,"trackID":"60125fcd-186e-402e-b8cc-e0a56984cf7a"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:21:00.559","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001178","trackID":"60125fcd-186e-402e-b8cc-e0a56984cf7a"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:22:03.926","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"ca81f555-f462-4133-a957-01ff3beff525","method":"POST","path":"/:3000127.0.0.1/api/v1/upload/zip","status":404,"duration":0,"trackID":"ca81f555-f462-4133-a957-01ff3beff525"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:22:03.957","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001179","trackID":"ca81f555-f462-4133-a957-01ff3beff525"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.959","caller":"ego/main.go:77","msg":"收到关闭信号,开始优雅关闭服务器..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.96","caller":"ego/main.go:87","msg":"HTTP服务器已优雅关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.961","caller":"conf/conf.go:63","msg":"开始关闭应用程序资源..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.961","caller":"conf/conf.go:69","msg":"数据库已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.962","caller":"cache/cache.go:93","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.962","caller":"conf/conf.go:76","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.962","caller":"conf/conf.go:79","msg":"应用程序资源关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:27.962","caller":"ego/main.go:93","msg":"服务器关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:32.987","caller":"sync/once.go:76","msg":"日志系统初始化成功","log_path":"./logs/","log_level":"info"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.148","caller":"conf/db.go:92","msg":"数据库连接成功","connString":"***已遮盖敏感信息***","maxOpenConns":20,"maxIdleConns":10}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.204","caller":"cache/cache.go:76","msg":"Redis连接成功","address":"www.suyun.store:6379","db":0,"pool_size":20,"read_timeout":3}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.204","caller":"util/jwt_utils.go:155","msg":"JWT密钥已从环境变量加载"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.204","caller":"conf/conf.go:57","msg":"所有配置初始化完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.204","caller":"ego/main.go:44","msg":"Gin模式设置完成","mode":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.204","caller":"middleware/cors.go:63","msg":"跨域配置初始化完成","允许方法":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"允许头":["Origin","Content-Length","Content-Type","Cookie","Authorization","X-Requested-With","X-CSRF-Token"],"允许来源":[],"MaxAge":43200,"模式":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.205","caller":"ego/main.go:70","msg":"服务器启动成功","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:33.205","caller":"ego/main.go:63","msg":"服务器启动中...","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:36.633","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"5cf28fd2-49d3-4784-80d3-78fa745ab292","method":"POST","path":"/:3000127.0.0.1/api/v1/upload/zip","status":404,"duration":0,"trackID":"5cf28fd2-49d3-4784-80d3-78fa745ab292"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:36.741","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001180","trackID":"5cf28fd2-49d3-4784-80d3-78fa745ab292"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.474","caller":"ego/main.go:77","msg":"收到关闭信号,开始优雅关闭服务器..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.474","caller":"ego/main.go:87","msg":"HTTP服务器已优雅关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.474","caller":"conf/conf.go:63","msg":"开始关闭应用程序资源..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.474","caller":"conf/conf.go:69","msg":"数据库已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.475","caller":"cache/cache.go:93","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.475","caller":"conf/conf.go:76","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.475","caller":"conf/conf.go:79","msg":"应用程序资源关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:23:48.475","caller":"ego/main.go:93","msg":"服务器关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.605","caller":"sync/once.go:76","msg":"日志系统初始化成功","log_path":"./logs/","log_level":"info"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.728","caller":"conf/db.go:92","msg":"数据库连接成功","connString":"***已遮盖敏感信息***","maxOpenConns":20,"maxIdleConns":10}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.774","caller":"cache/cache.go:76","msg":"Redis连接成功","address":"www.suyun.store:6379","db":0,"pool_size":20,"read_timeout":3}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.774","caller":"util/jwt_utils.go:155","msg":"JWT密钥已从环境变量加载"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.774","caller":"conf/conf.go:57","msg":"所有配置初始化完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.774","caller":"ego/main.go:44","msg":"Gin模式设置完成","mode":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.775","caller":"middleware/cors.go:63","msg":"跨域配置初始化完成","允许方法":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"允许头":["Origin","Content-Length","Content-Type","Cookie","Authorization","X-Requested-With","X-CSRF-Token"],"允许来源":[],"MaxAge":43200,"模式":"test"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.775","caller":"ego/main.go:70","msg":"服务器启动成功","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:24:31.775","caller":"ego/main.go:63","msg":"服务器启动中...","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:25:05.982","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"76d2a5f2-dd16-40a4-8bdb-9acff8c5f873","method":"POST","path":"/:3000127.0.0.1/api/v1/upload/zip","status":404,"duration":15.5752447,"trackID":"76d2a5f2-dd16-40a4-8bdb-9acff8c5f873"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:25:06.018","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001181","trackID":"76d2a5f2-dd16-40a4-8bdb-9acff8c5f873"}
|
||||||
|
{"level":"error","ts":"2025-07-16 14:26:13.328","caller":"service/sys_user_service.go:62","msg":"参数绑定错误!","error":"EOF","trackID":"64311c08-a64d-4658-84df-fe8c9037c417"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:26:13.419","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"64311c08-a64d-4658-84df-fe8c9037c417","method":"POST","path":"/api/v1/user/login","status":200,"duration":2.4723363,"trackID":"64311c08-a64d-4658-84df-fe8c9037c417"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:26:13.452","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001182","trackID":"64311c08-a64d-4658-84df-fe8c9037c417"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:26:48.039","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"8dc8c313-3973-46f6-9b20-2428288b3a8e","method":"POST","path":"/api/v1/upload/zip","status":200,"duration":2.6992122,"trackID":"8dc8c313-3973-46f6-9b20-2428288b3a8e"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:26:48.071","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001183","trackID":"8dc8c313-3973-46f6-9b20-2428288b3a8e"}
|
||||||
|
{"level":"error","ts":"2025-07-16 14:27:57.545","caller":"service/sys_upload_service.go:46","msg":"目标文件夹已存在!","trackID":"92742f89-9afd-4f1e-8cee-50ab55c547df"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:27:57.545","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"92742f89-9afd-4f1e-8cee-50ab55c547df","method":"POST","path":"/api/v1/upload/zip","status":200,"duration":15.0455176,"trackID":"92742f89-9afd-4f1e-8cee-50ab55c547df"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:27:57.605","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001184","trackID":"92742f89-9afd-4f1e-8cee-50ab55c547df"}
|
||||||
|
{"level":"error","ts":"2025-07-16 14:28:09.967","caller":"service/sys_upload_service.go:46","msg":"目标文件夹已存在!","trackID":"3f6df601-8965-4f4a-b791-7ba07e076ada"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:28:09.967","caller":"middleware/zap_log.go:25","msg":"Handled request","trackID":"3f6df601-8965-4f4a-b791-7ba07e076ada","method":"POST","path":"/api/v1/upload/zip","status":200,"duration":1.9901575,"trackID":"3f6df601-8965-4f4a-b791-7ba07e076ada"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:28:10","caller":"service/sys_oper_log_service.go:167","msg":"保存操作日志成功","OperId":"SOL0000001185","trackID":"3f6df601-8965-4f4a-b791-7ba07e076ada"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.669","caller":"ego/main.go:77","msg":"收到关闭信号,开始优雅关闭服务器..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.669","caller":"ego/main.go:87","msg":"HTTP服务器已优雅关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.669","caller":"conf/conf.go:63","msg":"开始关闭应用程序资源..."}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.67","caller":"conf/conf.go:69","msg":"数据库已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.67","caller":"cache/cache.go:93","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.67","caller":"conf/conf.go:76","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.67","caller":"conf/conf.go:79","msg":"应用程序资源关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-07-16 14:29:42.67","caller":"ego/main.go:93","msg":"服务器关闭完成"}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.443","caller":"sync/once.go:78","msg":"日志系统初始化成功","log_path":"./logs/","log_level":"info"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.69","caller":"conf/db.go:91","msg":"数据库连接成功","connString":"***已遮盖敏感信息***","maxOpenConns":20,"maxIdleConns":10}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.783","caller":"cache/cache.go:76","msg":"Redis连接成功","address":"www.suyun.store:6379","db":0,"pool_size":20,"read_timeout":3}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.783","caller":"util/jwt_utils.go:155","msg":"JWT密钥已从环境变量加载"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.783","caller":"conf/conf.go:57","msg":"所有配置初始化完成"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.783","caller":"ego/main.go:44","msg":"Gin模式设置完成","mode":"test"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.784","caller":"middleware/cors.go:63","msg":"跨域配置初始化完成","允许方法":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"允许头":["Origin","Content-Length","Content-Type","Cookie","Authorization","X-Requested-With","X-CSRF-Token"],"允许来源":[],"MaxAge":43200,"模式":"test"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.784","caller":"ego/main.go:70","msg":"服务器启动成功","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:47.784","caller":"ego/main.go:63","msg":"服务器启动中...","addr":":3000"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.21","caller":"ego/main.go:77","msg":"收到关闭信号,开始优雅关闭服务器..."}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.21","caller":"ego/main.go:87","msg":"HTTP服务器已优雅关闭"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.21","caller":"conf/conf.go:63","msg":"开始关闭应用程序资源..."}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.211","caller":"conf/conf.go:69","msg":"数据库已关闭"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.211","caller":"cache/cache.go:93","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.211","caller":"conf/conf.go:76","msg":"Redis已关闭"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.211","caller":"conf/conf.go:79","msg":"应用程序资源关闭完成"}
|
||||||
|
{"level":"info","ts":"2025-08-01 16:14:52.211","caller":"ego/main.go:93","msg":"服务器关闭完成"}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TRACKED = "trackID"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Logger *zap.Logger
|
||||||
|
loggerMux sync.Once
|
||||||
|
CallerSkip = 2
|
||||||
|
initErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildLogger 初始化日志系统
|
||||||
|
func BuildLogger() error {
|
||||||
|
loggerMux.Do(func() {
|
||||||
|
initErr = initLogger()
|
||||||
|
})
|
||||||
|
return initErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// initLogger 实际的日志初始化逻辑
|
||||||
|
func initLogger() error {
|
||||||
|
logLevel := getLogLevel()
|
||||||
|
logPath := os.Getenv("LOG_PATH")
|
||||||
|
if logPath == "" {
|
||||||
|
logPath = "./logs" // 默认日志路径
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试创建日志目录
|
||||||
|
if err := os.MkdirAll(logPath, 0755); err != nil {
|
||||||
|
// 使用标准库的log记录错误,因为zap还没初始化
|
||||||
|
log.Printf("无法创建日志目录 %s: %v", logPath, err)
|
||||||
|
return fmt.Errorf("无法创建日志目录 %s: %w", logPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置滚动日志(按天滚动,保留28天)
|
||||||
|
logFileName := filepath.Join(logPath, "ego.%Y%m%d.log")
|
||||||
|
logs, err := rotatelogs.New(
|
||||||
|
logFileName,
|
||||||
|
rotatelogs.WithMaxAge(28*24*time.Hour),
|
||||||
|
rotatelogs.WithRotationTime(24*time.Hour),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("创建滚动日志失败: %v", err)
|
||||||
|
return fmt.Errorf("创建滚动日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置控制台输出
|
||||||
|
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
|
||||||
|
consoleSyncer := zapcore.AddSync(os.Stdout)
|
||||||
|
|
||||||
|
// 配置文件输出(生产环境JSON格式)
|
||||||
|
prodEncoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
prodEncoderConfig.EncodeTime = TimeEncoder
|
||||||
|
prodEncoderConfig.CallerKey = "caller"
|
||||||
|
prodEncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
|
||||||
|
prodEncoder := zapcore.NewJSONEncoder(prodEncoderConfig)
|
||||||
|
fileSyncer := zapcore.AddSync(logs)
|
||||||
|
|
||||||
|
// 创建日志核心
|
||||||
|
core := zapcore.NewTee(
|
||||||
|
zapcore.NewCore(consoleEncoder, consoleSyncer, logLevel),
|
||||||
|
zapcore.NewCore(prodEncoder, fileSyncer, logLevel),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化 Logger
|
||||||
|
Logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(CallerSkip))
|
||||||
|
|
||||||
|
// 记录初始化成功
|
||||||
|
Logger.Info("日志系统初始化成功",
|
||||||
|
zap.String("log_path", logPath),
|
||||||
|
zap.String("log_level", logLevel.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug 记录调试日志
|
||||||
|
func Debug(c *gin.Context, msg string, fields ...zap.Field) {
|
||||||
|
if Logger != nil {
|
||||||
|
logWithTrack(c, zap.DebugLevel, msg, fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info 记录信息日志
|
||||||
|
func Info(c *gin.Context, msg string, fields ...zap.Field) {
|
||||||
|
if Logger != nil {
|
||||||
|
logWithTrack(c, zap.InfoLevel, msg, fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn 记录警告日志
|
||||||
|
func Warn(c *gin.Context, msg string, fields ...zap.Field) {
|
||||||
|
if Logger != nil {
|
||||||
|
logWithTrack(c, zap.WarnLevel, msg, fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 记录错误日志
|
||||||
|
func Error(c *gin.Context, msg string, fields ...zap.Field) {
|
||||||
|
if Logger != nil {
|
||||||
|
logWithTrack(c, zap.ErrorLevel, msg, fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logWithTrack 封装带 trackID 的日志记录
|
||||||
|
func logWithTrack(c *gin.Context, level zapcore.Level, msg string, fields ...zap.Field) {
|
||||||
|
if Logger == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
Logger.Check(level, msg).Write(fields...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trackID := GetTrackID(c)
|
||||||
|
Logger.Check(level, msg).Write(append(fields, zap.String("trackID", trackID))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrackID 获取或生成 trackID
|
||||||
|
func GetTrackID(c *gin.Context) string {
|
||||||
|
if trackID, exists := c.Get(TRACKED); exists {
|
||||||
|
return trackID.(string)
|
||||||
|
}
|
||||||
|
if random, err := uuid.NewRandom(); err == nil {
|
||||||
|
// 生成新的 trackID 并存入上下文
|
||||||
|
newTrackID := strings.ToLower(random.String())
|
||||||
|
c.Set(TRACKED, newTrackID)
|
||||||
|
return newTrackID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLogLevel 根据环境变量获取日志级别
|
||||||
|
func getLogLevel() zapcore.Level {
|
||||||
|
switch strings.ToLower(os.Getenv("LOG_LEVEL")) {
|
||||||
|
case "debug":
|
||||||
|
return zap.DebugLevel
|
||||||
|
case "info":
|
||||||
|
return zap.InfoLevel
|
||||||
|
case "warn":
|
||||||
|
return zap.WarnLevel
|
||||||
|
case "error":
|
||||||
|
return zap.ErrorLevel
|
||||||
|
default:
|
||||||
|
return zap.InfoLevel // 默认使用 Info 级别
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeEncoder 自定义时间格式
|
||||||
|
func TimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||||
|
enc.AppendString(t.Format("2006-01-02 15:04:05.999"))
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@echo off
|
||||||
|
echo 生成 Swagger 文档...
|
||||||
|
|
||||||
|
|
||||||
|
echo 生成完成!
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
echo 生成 Swagger 文档...
|
||||||
|
|
||||||
|
|
||||||
|
echo 生成完成!
|
|
@ -0,0 +1,57 @@
|
||||||
|
-- 部署文件记录表
|
||||||
|
CREATE TABLE `sys_deploy_file` (
|
||||||
|
`deploy_id` varchar(64) NOT NULL COMMENT '部署ID',
|
||||||
|
`file_name` varchar(255) NOT NULL COMMENT '原始文件名',
|
||||||
|
`project_name` varchar(100) NOT NULL COMMENT '项目名称',
|
||||||
|
`domain` varchar(255) NOT NULL COMMENT '访问域名',
|
||||||
|
`deploy_path` varchar(500) NOT NULL COMMENT '部署路径',
|
||||||
|
`file_size` bigint DEFAULT NULL COMMENT '文件大小(字节)',
|
||||||
|
`file_hash` varchar(64) DEFAULT NULL COMMENT '文件哈希值',
|
||||||
|
`status` char(1) DEFAULT '1' COMMENT '状态(0停用 1正常 2部署中 3部署失败)',
|
||||||
|
`deploy_status` char(1) DEFAULT '0' COMMENT '部署状态(0未部署 1部署成功 2部署失败)',
|
||||||
|
`error_msg` text COMMENT '错误信息',
|
||||||
|
`version` varchar(50) DEFAULT NULL COMMENT '版本号',
|
||||||
|
`description` varchar(500) DEFAULT NULL COMMENT '描述',
|
||||||
|
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||||
|
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
||||||
|
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`deploy_time` datetime DEFAULT NULL COMMENT '部署时间',
|
||||||
|
`last_access_time` datetime DEFAULT NULL COMMENT '最后访问时间',
|
||||||
|
`access_count` bigint DEFAULT '0' COMMENT '访问次数',
|
||||||
|
PRIMARY KEY (`deploy_id`),
|
||||||
|
KEY `idx_project_name` (`project_name`),
|
||||||
|
KEY `idx_domain` (`domain`),
|
||||||
|
KEY `idx_status` (`status`),
|
||||||
|
KEY `idx_deploy_status` (`deploy_status`),
|
||||||
|
KEY `idx_create_time` (`create_time`),
|
||||||
|
KEY `idx_del_flag` (`del_flag`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部署文件记录表';
|
||||||
|
|
||||||
|
-- 插入示例数据
|
||||||
|
INSERT INTO `sys_deploy_file` (
|
||||||
|
`deploy_id`,
|
||||||
|
`file_name`,
|
||||||
|
`project_name`,
|
||||||
|
`domain`,
|
||||||
|
`deploy_path`,
|
||||||
|
`file_size`,
|
||||||
|
`status`,
|
||||||
|
`deploy_status`,
|
||||||
|
`description`,
|
||||||
|
`create_by`,
|
||||||
|
`create_time`
|
||||||
|
) VALUES (
|
||||||
|
'DEPLOY001',
|
||||||
|
'my-project-v1.0.0.zip',
|
||||||
|
'my-project',
|
||||||
|
'my-project.unbug.cn',
|
||||||
|
'/home/my-project',
|
||||||
|
1048576,
|
||||||
|
'1',
|
||||||
|
'1',
|
||||||
|
'示例项目部署文件',
|
||||||
|
'admin',
|
||||||
|
NOW()
|
||||||
|
);
|
|
@ -0,0 +1,56 @@
|
||||||
|
create table if not exists ego.sys_login_log
|
||||||
|
(
|
||||||
|
id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '访问ID' primary key,
|
||||||
|
user_id varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '用户ID',
|
||||||
|
ip_addr varchar
|
||||||
|
(
|
||||||
|
128
|
||||||
|
) default '' null comment '登录IP地址',
|
||||||
|
login_location varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) default '' null comment '登录地点',
|
||||||
|
browser varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '浏览器类型',
|
||||||
|
os varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '操作系统',
|
||||||
|
status varchar
|
||||||
|
(
|
||||||
|
10
|
||||||
|
) default '0' null comment '登录状态(0成功 1失败)',
|
||||||
|
msg varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) default '' null comment '提示消息',
|
||||||
|
login_time datetime null comment '登录时间',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注'
|
||||||
|
) comment '系统访问记录' charset = utf8mb3;
|
||||||
|
|
||||||
|
-- 创建索引
|
||||||
|
create index idx_user_id on ego.sys_login_log (user_id);
|
||||||
|
create index idx_login_time on ego.sys_login_log (login_time);
|
||||||
|
create index idx_status on ego.sys_login_log (status);
|
|
@ -0,0 +1,638 @@
|
||||||
|
create table if not exists ego.sys_config
|
||||||
|
(
|
||||||
|
config_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '参数主键'
|
||||||
|
primary key,
|
||||||
|
config_name varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '参数名称',
|
||||||
|
config_key varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '参数键名',
|
||||||
|
config_value varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) default '' null comment '参数键值',
|
||||||
|
config_type char default 'N' null comment '系统内置(Y是 N否)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注'
|
||||||
|
)
|
||||||
|
comment '参数配置表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_dept
|
||||||
|
(
|
||||||
|
dept_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '部门id'
|
||||||
|
primary key,
|
||||||
|
parent_id varchar(64) default '0' null comment '父部门id',
|
||||||
|
ancestors varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '祖级列表',
|
||||||
|
dept_name varchar
|
||||||
|
(
|
||||||
|
30
|
||||||
|
) default '' null comment '部门名称',
|
||||||
|
order_num int default 0 null comment '显示顺序',
|
||||||
|
leader varchar
|
||||||
|
(
|
||||||
|
20
|
||||||
|
) null comment '负责人',
|
||||||
|
phone varchar
|
||||||
|
(
|
||||||
|
11
|
||||||
|
) null comment '联系电话',
|
||||||
|
email varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) null comment '邮箱',
|
||||||
|
status char default '0' null comment '部门状态(0正常 1停用)',
|
||||||
|
del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间'
|
||||||
|
)
|
||||||
|
comment '部门表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_dict_data
|
||||||
|
(
|
||||||
|
dict_code
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '字典编码'
|
||||||
|
primary key,
|
||||||
|
dict_sort int default 0 null comment '字典排序',
|
||||||
|
dict_label varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '字典标签',
|
||||||
|
dict_value varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '字典键值',
|
||||||
|
dict_type varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '字典类型',
|
||||||
|
css_class varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) null comment '样式属性(其他样式扩展)',
|
||||||
|
list_class varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) null comment '表格回显样式',
|
||||||
|
is_default char default 'N' null comment '是否默认(Y是 N否)',
|
||||||
|
status char default '0' null comment '状态(0正常 1停用)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注'
|
||||||
|
)
|
||||||
|
comment '字典数据表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_dict_type
|
||||||
|
(
|
||||||
|
dict_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '字典主键'
|
||||||
|
primary key,
|
||||||
|
dict_name varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '字典名称',
|
||||||
|
dict_type varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '字典类型',
|
||||||
|
status char default '0' null comment '状态(0正常 1停用)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注',
|
||||||
|
constraint dict_type
|
||||||
|
unique
|
||||||
|
(
|
||||||
|
dict_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
comment '字典类型表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_file
|
||||||
|
(
|
||||||
|
id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '主键'
|
||||||
|
primary key,
|
||||||
|
file_name varchar
|
||||||
|
(
|
||||||
|
128
|
||||||
|
) not null comment '文件名称',
|
||||||
|
file_type varchar
|
||||||
|
(
|
||||||
|
32
|
||||||
|
) null comment '文件类型',
|
||||||
|
file_size bigint null comment '文件大小',
|
||||||
|
file_key varchar
|
||||||
|
(
|
||||||
|
128
|
||||||
|
) null comment '文件Key',
|
||||||
|
type varchar
|
||||||
|
(
|
||||||
|
4
|
||||||
|
) null comment '业务类型',
|
||||||
|
business_id varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) null comment '业务主键',
|
||||||
|
business_type varchar
|
||||||
|
(
|
||||||
|
32
|
||||||
|
) null comment '业务类型',
|
||||||
|
del_flag varchar(32) default '0' null comment '是否删除',
|
||||||
|
revision int default 0 null comment '乐观锁',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
32
|
||||||
|
) null comment '创建人',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
32
|
||||||
|
) null comment '更新人',
|
||||||
|
update_time datetime null comment '更新时间'
|
||||||
|
)
|
||||||
|
comment 'sys_file 文件表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create index businessId_index
|
||||||
|
on ego.sys_file (business_id);
|
||||||
|
|
||||||
|
create table if not exists ego.sys_job
|
||||||
|
(
|
||||||
|
job_id varchar(64) not null comment '任务ID',
|
||||||
|
job_name varchar(64) default '' not null comment '任务名称',
|
||||||
|
job_group varchar(64) default 'DEFAULT' not null comment '任务组名',
|
||||||
|
invoke_target varchar(500) not null comment '调用目标字符串',
|
||||||
|
cron_expression varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) default '' null comment 'cron执行表达式',
|
||||||
|
misfire_policy varchar
|
||||||
|
(
|
||||||
|
20
|
||||||
|
) default '3' null comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
|
||||||
|
concurrent char default '1' null comment '是否并发执行(0允许 1禁止)',
|
||||||
|
status char default '0' null comment '状态(0正常 1暂停)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) default '' null comment '备注信息',
|
||||||
|
primary key (job_id, job_name, job_group)
|
||||||
|
)
|
||||||
|
comment '定时任务调度表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_job_log
|
||||||
|
(
|
||||||
|
job_log_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '任务日志ID'
|
||||||
|
primary key,
|
||||||
|
job_name varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '任务名称',
|
||||||
|
job_group varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '任务组名',
|
||||||
|
invoke_target varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) not null comment '调用目标字符串',
|
||||||
|
job_message varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '日志信息',
|
||||||
|
status char default '0' null comment '执行状态(0正常 1失败)',
|
||||||
|
exception_info varchar
|
||||||
|
(
|
||||||
|
2000
|
||||||
|
) default '' null comment '异常信息',
|
||||||
|
create_time datetime null comment '创建时间'
|
||||||
|
)
|
||||||
|
comment '定时任务调度日志表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_logininfor
|
||||||
|
(
|
||||||
|
info_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '访问ID'
|
||||||
|
primary key,
|
||||||
|
user_name varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '用户账号',
|
||||||
|
ipaddr varchar
|
||||||
|
(
|
||||||
|
128
|
||||||
|
) default '' null comment '登录IP地址',
|
||||||
|
login_location varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) default '' null comment '登录地点',
|
||||||
|
browser varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '浏览器类型',
|
||||||
|
os varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '操作系统',
|
||||||
|
status char default '0' null comment '登录状态(0成功 1失败)',
|
||||||
|
msg varchar
|
||||||
|
(
|
||||||
|
1024
|
||||||
|
) default '' null comment '提示消息',
|
||||||
|
login_time datetime null comment '访问时间'
|
||||||
|
)
|
||||||
|
comment '系统访问记录' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_menu
|
||||||
|
(
|
||||||
|
menu_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '菜单ID'
|
||||||
|
primary key,
|
||||||
|
menu_name varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) not null comment '菜单名称',
|
||||||
|
parent_id varchar(64) default '0' null comment '父菜单ID',
|
||||||
|
order_num int default 0 null comment '显示顺序',
|
||||||
|
path varchar
|
||||||
|
(
|
||||||
|
200
|
||||||
|
) default '' null comment '路由地址',
|
||||||
|
component varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) null comment '组件路径',
|
||||||
|
query varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) null comment '路由参数',
|
||||||
|
is_frame char(2) default '1' null comment '是否为外链(0是 1否)',
|
||||||
|
is_cache char(2) default '0' null comment '是否缓存(0缓存 1不缓存)',
|
||||||
|
menu_type char default '' null comment '菜单类型(M目录 C菜单 F按钮)',
|
||||||
|
visible char default '0' null comment '菜单状态(0显示 1隐藏)',
|
||||||
|
status char default '0' null comment '菜单状态(0正常 1停用)',
|
||||||
|
perms varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) null comment '权限标识',
|
||||||
|
icon varchar(100) default '#' null comment '菜单图标',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) default '' null comment '备注'
|
||||||
|
)
|
||||||
|
comment '菜单权限表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_notice
|
||||||
|
(
|
||||||
|
notice_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '公告ID'
|
||||||
|
primary key,
|
||||||
|
notice_title varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) not null comment '公告标题',
|
||||||
|
notice_type char not null comment '公告类型(1通知 2公告)',
|
||||||
|
notice_content longtext null comment '公告内容',
|
||||||
|
status char default '0' null comment '公告状态(0正常 1关闭)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) null comment '备注'
|
||||||
|
)
|
||||||
|
comment '通知公告表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_oper_log
|
||||||
|
(
|
||||||
|
oper_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '日志主键'
|
||||||
|
primary key,
|
||||||
|
title varchar(50) default '' null comment '模块标题',
|
||||||
|
business_type int default 0 null comment '业务类型(0其它 1新增 2修改 3删除)',
|
||||||
|
method varchar(100) default '' null comment '方法名称',
|
||||||
|
request_method varchar(64) default '' null comment '请求方式',
|
||||||
|
operator_type int default 0 null comment '操作类别(0其它 1后台用户 2手机端用户)',
|
||||||
|
oper_name varchar(50) default '' null comment '操作人员',
|
||||||
|
dept_name varchar(50) default '' null comment '部门名称',
|
||||||
|
oper_url varchar(255) default '' null comment '请求URL',
|
||||||
|
oper_ip varchar(128) default '' null comment '主机地址',
|
||||||
|
oper_location varchar(255) default '' null comment '操作地点',
|
||||||
|
oper_param varchar(2000) default '' null comment '请求参数',
|
||||||
|
json_result varchar(2000) default '' null comment '返回参数',
|
||||||
|
status int default 0 null comment '操作状态(0正常 1异常)',
|
||||||
|
error_msg varchar(2000) default '' null comment '错误消息',
|
||||||
|
oper_time datetime null comment '操作时间'
|
||||||
|
)
|
||||||
|
comment '操作日志记录' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_post
|
||||||
|
(
|
||||||
|
post_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '岗位ID'
|
||||||
|
primary key,
|
||||||
|
post_code varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '岗位编码',
|
||||||
|
post_name varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) not null comment '岗位名称',
|
||||||
|
post_sort int not null comment '显示顺序',
|
||||||
|
status char not null comment '状态(0正常 1停用)',
|
||||||
|
create_by varchar(64) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar(64) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注'
|
||||||
|
)
|
||||||
|
comment '岗位信息表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_role
|
||||||
|
(
|
||||||
|
role_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '角色ID'
|
||||||
|
primary key,
|
||||||
|
role_name varchar
|
||||||
|
(
|
||||||
|
30
|
||||||
|
) not null comment '角色名称',
|
||||||
|
role_key varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) not null comment '角色权限字符串',
|
||||||
|
role_sort int not null comment '显示顺序',
|
||||||
|
data_scope char default '1' null comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
|
||||||
|
menu_check_strictly tinyint(1) default 1 null comment '菜单树选择项是否关联显示',
|
||||||
|
dept_check_strictly tinyint(1) default 1 null comment '部门树选择项是否关联显示',
|
||||||
|
status char not null comment '角色状态(0正常 1停用)',
|
||||||
|
del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注'
|
||||||
|
)
|
||||||
|
comment '角色信息表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_role_dept
|
||||||
|
(
|
||||||
|
role_id varchar(64) not null comment '角色ID',
|
||||||
|
dept_id varchar(64) not null comment '部门ID',
|
||||||
|
primary key (role_id, dept_id)
|
||||||
|
)
|
||||||
|
comment '角色和部门关联表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_role_menu
|
||||||
|
(
|
||||||
|
role_id varchar(64) not null comment '角色ID',
|
||||||
|
menu_id varchar(64) not null comment '菜单ID',
|
||||||
|
primary key (role_id, menu_id)
|
||||||
|
)
|
||||||
|
comment '角色和菜单关联表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_sequence
|
||||||
|
(
|
||||||
|
table_name varchar(32) not null comment '表名',
|
||||||
|
seq int not null comment '序列',
|
||||||
|
prefix varchar(32) not null comment '前缀',
|
||||||
|
primary key (table_name, seq, prefix)
|
||||||
|
)
|
||||||
|
comment '系统序列表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_user
|
||||||
|
(
|
||||||
|
user_id
|
||||||
|
varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) not null comment '用户ID'
|
||||||
|
primary key,
|
||||||
|
dept_id varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) null comment '部门ID',
|
||||||
|
user_name varchar
|
||||||
|
(
|
||||||
|
30
|
||||||
|
) not null comment '用户账号',
|
||||||
|
nick_name varchar
|
||||||
|
(
|
||||||
|
30
|
||||||
|
) not null comment '用户昵称',
|
||||||
|
user_type varchar(2) default '00' null comment '用户类型(00系统用户)',
|
||||||
|
email varchar
|
||||||
|
(
|
||||||
|
50
|
||||||
|
) default '' null comment '用户邮箱',
|
||||||
|
phone_number varchar
|
||||||
|
(
|
||||||
|
11
|
||||||
|
) default '' null comment '手机号码',
|
||||||
|
solt int null comment '排序',
|
||||||
|
gender char default '0' null comment '用户性别(0男 1女 2未知)',
|
||||||
|
avatar varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '头像地址',
|
||||||
|
pass_word varchar
|
||||||
|
(
|
||||||
|
100
|
||||||
|
) default '' null comment '密码',
|
||||||
|
status char default '0' null comment '帐号状态(0正常 1停用)',
|
||||||
|
del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)',
|
||||||
|
login_ip varchar
|
||||||
|
(
|
||||||
|
128
|
||||||
|
) default '' null comment '最后登录IP',
|
||||||
|
login_date datetime null comment '最后登录时间',
|
||||||
|
resource_invoke varchar
|
||||||
|
(
|
||||||
|
255
|
||||||
|
) null comment '资源来源映射,多个用,分割',
|
||||||
|
create_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '创建者',
|
||||||
|
create_time datetime null comment '创建时间',
|
||||||
|
update_by varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) default '' null comment '更新者',
|
||||||
|
update_time datetime null comment '更新时间',
|
||||||
|
remark varchar
|
||||||
|
(
|
||||||
|
500
|
||||||
|
) null comment '备注',
|
||||||
|
select_key varchar
|
||||||
|
(
|
||||||
|
64
|
||||||
|
) null comment '动态验证'
|
||||||
|
)
|
||||||
|
comment '用户信息表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_user_post
|
||||||
|
(
|
||||||
|
user_id varchar(64) not null comment '用户ID',
|
||||||
|
post_id varchar(64) not null comment '岗位ID',
|
||||||
|
primary key (user_id, post_id)
|
||||||
|
)
|
||||||
|
comment '用户与岗位关联表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create table if not exists ego.sys_user_role
|
||||||
|
(
|
||||||
|
user_id varchar(64) not null comment '用户ID',
|
||||||
|
role_id varchar(64) not null comment '角色ID',
|
||||||
|
primary key (user_id, role_id)
|
||||||
|
)
|
||||||
|
comment '用户和角色关联表' charset = utf8mb3;
|
||||||
|
|
||||||
|
create
|
||||||
|
definer = root@`%` function ego.CURRVAL(seq_name varchar(30)) returns varchar(50) deterministic
|
||||||
|
BEGIN
|
||||||
|
DECLARE
|
||||||
|
seq_val INT;
|
||||||
|
DECLARE
|
||||||
|
prefix_val VARCHAR(32);
|
||||||
|
SELECT seq, prefix
|
||||||
|
INTO seq_val, prefix_val
|
||||||
|
FROM sys_sequence
|
||||||
|
WHERE table_name = seq_name;
|
||||||
|
RETURN concat(prefix_val, LPAD(seq_val, 10, '0'));
|
||||||
|
END;
|
||||||
|
|
||||||
|
create
|
||||||
|
definer = root@`%` function ego.NEXTVAL(seq_name varchar(30)) returns varchar(30) deterministic
|
||||||
|
BEGIN
|
||||||
|
UPDATE sys_sequence
|
||||||
|
SET SEQ = SEQ + 1
|
||||||
|
WHERE TABLE_NAME = seq_name;
|
||||||
|
RETURN CURRVAL(seq_name);
|
||||||
|
END;
|
Loading…
Reference in New Issue