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