first commit

This commit is contained in:
Daniel 2025-06-19 10:30:46 +08:00
commit 91436ca88a
156 changed files with 29188 additions and 0 deletions

5
.devcontainer/.env Normal file
View File

@ -0,0 +1,5 @@
POSTGRES_DB=ginadmin
POSTGRES_USER=postgres
POSTGRES_PASSWORD=123456
DATABASE_URL=postgres://postgres:123456@db:5432/ginadmin

13
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM mcr.microsoft.com/devcontainers/go:1.22-bookworm
ARG APP=hailinservice
# Set CGO_CFLAGS to enable large file support
ENV CGO_CFLAGS "-D_LARGEFILE64_SOURCE"
RUN go install github.com/google/wire/cmd/wire@latest \
&& go install github.com/swaggo/swag/cmd/swag@latest \
&& go install github.com/gin-admin/gin-admin-cli/v10@latest \
&& chown -R vscode /go

View File

@ -0,0 +1,9 @@
{
"name": "ginadmin",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"forwardPorts": [
8040
]
}

View File

@ -0,0 +1,42 @@
version: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
command: sleep infinity
networks:
- db
- redis
volumes:
- ../..:/workspaces:cached
env_file:
- .env
db:
image: postgres:15.3-alpine
restart: unless-stopped
ports:
- 5432:5432
networks:
- db
volumes:
- postgres-data:/var/lib/postgresql/data
env_file:
- .env
redis:
image: redis:latest
restart: unless-stopped
ports:
- 6379:6379
networks:
- redis
volumes:
postgres-data:
networks:
db:
redis:

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
*.DS_Store
/hailinservice
/hailinservice_linux_amd64
/hailinservice.lock
/release
/data
/uploads
/internal/test/data
tmp
/vendor
/configs/gen_rbac_policy.csv
/configs/gen_rbac_policy.csv.bak
/configs/rbac_policy.csv.bak
/test/data
/internal/swagger/v3/.openapi-generator
/internal/swagger/v3/.openapi-generator-ignore
# IDE configs
.idea
.vscode

28
Dockerfile Normal file
View File

@ -0,0 +1,28 @@
FROM golang:alpine as builder
ARG APP=hailinservice
ARG VERSION=v1.0.0
ARG RELEASE_TAG=$(VERSION)
# Install the required packages
RUN apk add --no-cache gcc musl-dev sqlite-dev
# Set CGO_CFLAGS to enable large file support
ENV CGO_CFLAGS "-D_LARGEFILE64_SOURCE"
ENV GOPROXY="https://goproxy.cn"
WORKDIR /go/src/${APP}
COPY . .
# Build the application
RUN go build -ldflags "-w -s -X main.VERSION=${RELEASE_TAG}" -o ./${APP} .
FROM alpine
ARG APP=hailinservice
WORKDIR /go/src/${APP}
COPY --from=builder /go/src/${APP}/${APP} /usr/bin/
# COPY --from=builder /go/src/${APP}/configs /usr/bin/configs
# COPY --from=builder /go/src/${APP}/dist /usr/bin/dist
ENTRYPOINT ["hailinservice", "start", "-d", "/usr/bin/configs", "-c", "prod", "-s", "/usr/bin/dist"]
EXPOSE 8040

51
Makefile Normal file
View File

@ -0,0 +1,51 @@
.PHONY: start build
NOW = $(shell date -u '+%Y%m%d%I%M%S')
RELEASE_VERSION = v1.0.0
APP = hailinservice
SERVER_BIN = ${APP}
GIT_COUNT = $(shell git rev-list --all --count)
GIT_HASH = $(shell git rev-parse --short HEAD)
RELEASE_TAG = $(RELEASE_VERSION).$(GIT_COUNT).$(GIT_HASH)
CONFIG_DIR = ./configs
CONFIG_FILES = dev
STATIC_DIR = ./build/dist
START_ARGS = -d $(CONFIG_DIR) -c $(CONFIG_FILES) -s $(STATIC_DIR)
all: start
start:
@go run -ldflags "-X main.VERSION=$(RELEASE_TAG)" main.go start $(START_ARGS)
build:
@go build -ldflags "-w -s -X main.VERSION=$(RELEASE_TAG)" -o $(SERVER_BIN)
build-linux:
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux-musl" CXX="zig c++ -target x86_64-linux-musl" CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -ldflags "-w -s -X main.VERSION=$(RELEASE_TAG)" -o $(SERVER_BIN)_linux_amd64
# go install github.com/google/wire/cmd/wire@latest
wire:
@wire gen ./internal/wirex
# go install github.com/swaggo/swag/cmd/swag@latest
swagger:
@swag init --parseDependency --generalInfo ./main.go --output ./internal/swagger
# https://github.com/OpenAPITools/openapi-generator
openapi:
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/internal/swagger/swagger.yaml -g openapi -o /local/internal/swagger/v3
clean:
rm -rf data $(SERVER_BIN)
serve: build
./$(SERVER_BIN) start $(START_ARGS)
serve-d: build
./$(SERVER_BIN) start $(START_ARGS) -d
stop:
./$(SERVER_BIN) stop

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# hailinService
> Hailinservice API service
## Quick Start
```bash
make start
```
## Build
```bash
make build
```
## Generate wire inject files
```bash
make wire
```
## Generate swagger documents
```bash
make swagger
```

250
README_EN.md Normal file
View File

@ -0,0 +1,250 @@
# [Gin](https://github.com/gin-gonic/gin)-Admin
> A lightweight, flexible, elegant and full-featured RBAC scaffolding based on Golang + Gin + GORM 2.0 + Casbin 2.0 + Wire DI.
English | [中文](README.md)
[![LICENSE](https://img.shields.io/github/license/LyricTian/gin-admin.svg)](https://github.com/LyricTian/gin-admin/blob/main/LICENSE)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
[![Go Report Card](https://goreportcard.com/badge/github.com/LyricTian/gin-admin)](https://goreportcard.com/report/github.com/LyricTian/gin-admin)
[![GitHub release](https://img.shields.io/github/tag/LyricTian/gin-admin.svg?label=release)](https://github.com/LyricTian/gin-admin/releases)
[![GitHub release date](https://img.shields.io/github/release-date/LyricTian/gin-admin.svg)](https://github.com/LyricTian/gin-admin/releases)
[![GoDoc](https://img.shields.io/badge/Godoc-reference-blue.svg)](https://godoc.org/github.com/LyricTian/gin-admin)
## Features
- :scroll: Elegant implementation of `RESTful API`, using interface-based programming paradigm to make your API design more professional and standardized
- :house: Adopts clear and concise modular architecture, making code structure clear at a glance, maintenance and upgrades more effortless
- :rocket: Based on high-performance `GIN` framework, integrating rich and practical middleware (authentication, CORS, logging, rate limiting, tracing, permission control, fault tolerance, compression, etc.), helping you quickly build enterprise-level applications
- :closed_lock_with_key: Integrates industry-leading `Casbin` permission framework, flexible and precise RBAC permission control makes security protection rock solid
- :page_facing_up: Based on powerful `GORM 2.0` ORM framework, elegantly handles database operations, greatly improving development efficiency
- :electric_plug: Innovatively adopts `WIRE` dependency injection, revolutionarily simplifies module dependency relationships, making code more elegant and decoupled
- :memo: Based on high-performance `Zap` logging framework, coupled with Context tracing, making system running status clear and transparent, problem troubleshooting nowhere to hide
- :key: Integrates time-tested `JWT` authentication mechanism, making user identity verification more secure and reliable
- :microscope: Automatically integrates `Swagger` API documentation, real-time API documentation updates, making development and debugging easier - [Online Demo](https://demo.ginadmin.top/swagger/index.html)
- :wrench: Complete unit testing system, based on `testify` framework to ensure system quality, leaving no place for bugs to hide
- :100: Adopts stateless design, supports horizontal scaling, paired with Redis to implement dynamic permission management, letting your system easily handle high concurrency
- :hammer: Developer's blessing! Powerful scaffolding tool [gin-admin-cli](https://github.com/gin-admin/gin-admin-cli), making your development work twice as efficient
![demo](./demo.png)
![swagger](./swagger.png)
## Frontend Projects
- [Frontend project based on Ant Design React](https://github.com/gin-admin/gin-admin-frontend)
- [Frontend project based on Vue.js](https://github.com/gin-admin/gin-admin-vue)
## Install Dependencies
- [Go](https://golang.org/) 1.19+
- [Wire](github.com/google/wire) `go install github.com/google/wire/cmd/wire@latest`
- [Swag](github.com/swaggo/swag) `go install github.com/swaggo/swag/cmd/swag@latest`
- [GIN-ADMIN-CLI](https://github.com/gin-admin/gin-admin-cli) `go install github.com/gin-admin/gin-admin-cli/v10@latest`
## Quick Start
### Create a New Project
> You can view detailed command instructions via `gin-admin-cli help new`
```bash
gin-admin-cli new -d ~/go/src --name testapp --desc 'A test API service based on golang.' --pkg 'github.com/xxx/testapp' --git-url https://gitee.com/lyric/gin-admin.git
```
### Start the Service
> You can switch to Chinese menu by changing `MenuFile = "menu_cn.json"` in the `configs/dev/server.toml` configuration file
```bash
cd ~/go/src/testapp
make start
# or
go run main.go start
```
### Compile the Service
```bash
make build
# or
go build -ldflags "-w -s -X main.VERSION=v1.0.0" -o testapp
```
### Generate Docker Image
```bash
docker build -f ./Dockerfile -t testapp:v1.0.0 .
```
### Generate Codes
> You can view detailed command instructions via `gin-admin-cli help gen`
#### Prepare Configuration File `dictionary.yaml`
```yaml
- name: Dictionary
comment: Dictionary management
disable_pagination: true
fill_gorm_commit: true
fill_router_prefix: true
tpl_type: "tree"
fields:
- name: Code
type: string
comment: Code of dictionary (unique for same parent)
gorm_tag: "size:32;"
form:
binding_tag: "required,max=32"
- name: Name
type: string
comment: Display name of dictionary
gorm_tag: "size:128;index"
query:
name: LikeName
in_query: true
form_tag: name
op: LIKE
form:
binding_tag: "required,max=128"
- name: Description
type: string
comment: Details about dictionary
gorm_tag: "size:1024"
form: {}
- name: Sequence
type: int
comment: Sequence for sorting
gorm_tag: "index;"
order: DESC
form: {}
- name: Status
type: string
comment: Status of dictionary (disabled, enabled)
gorm_tag: "size:20;index"
query: {}
form:
binding_tag: "required,oneof=disabled enabled"
```
```bash
gin-admin-cli gen -d . -m SYS -c dictionary.yaml
```
### Delete Function Module
> You can view detailed command instructions via `gin-admin-cli help remove`
```bash
gin-admin-cli rm -d . -m CMS --structs Article
```
### Generate Swagger Documentation
> You can generate Swagger documentation automatically via [Swag](github.com/swaggo/swag)
```bash
make swagger
# or
swag init --parseDependency --generalInfo ./main.go --output ./internal/swagger
```
### Generate Dependency Injection Code
> Dependency injection itself is used to solve the initial process of layer dependency among various modules, and you can generate dependency injection code automatically via [Wire](github.com/google/wire) to simplify the dependency injection process.
```bash
make wire
# or
wire gen ./internal/wirex
```
## Project Structure Overview
```text
├── cmd (Command line definition directory)
│ ├── start.go (Start command)
│ ├── stop.go (Stop command)
│ └── version.go (Version command)
├── configs
│ ├── dev
│ │ ├── logging.toml (Logging configuration file)
│ │ ├── middleware.toml (Middleware configuration file)
│ │ └── server.toml (Service configuration file)
│ ├── menu.json (Initialization menu file)
│ └── rbac_model.conf (Casbin RBAC model configuration file)
├── internal
│ ├── bootstrap (Initialization directory)
│ │ ├── bootstrap.go (Initialization)
│ │ ├── http.go (HTTP service)
│ │ └── logger.go (Logging service)
│ ├── config (Configuration file directory)
│ │ ├── config.go (Configuration file initialization)
│ │ ├── consts.go (Constant definition)
│ │ ├── middleware.go (Middleware configuration)
│ │ └── parse.go (Configuration file parsing)
│ ├── mods
│ │ ├── rbac (RBAC module)
│ │ │ ├── api (API layer)
│ │ │ ├── biz (Business logic layer)
│ │ │ ├── dal (Data access layer)
│ │ │ ├── schema (Data model layer)
│ │ │ ├── casbin.go (Casbin initialization)
│ │ │ ├── main.go (RBAC module entry)
│ │ │ └── wire.go (RBAC dependency injection initialization)
│ │ └── mods.go
│ ├── utility
│ │ └── prom
│ │ └── prom.go (Prometheus monitoring, used for integration with prometheus)
│ └── wirex (Dependency injection directory, contains the definition and initialization of dependency groups)
│ ├── injector.go
│ ├── wire.go
│ └── wire_gen.go
├── pkg (Public package directory)
│ ├── cachex (Cache package)
│ ├── crypto (Encryption package)
│ │ ├── aes (AES encryption)
│ │ ├── hash (Hash encryption)
│ │ └── rand (Random number)
│ ├── encoding (Encoding package)
│ │ ├── json (JSON encoding)
│ │ ├── toml (TOML encoding)
│ │ └── yaml (YAML encoding)
│ ├── errors (Error handling package)
│ ├── gormx (Gorm extension package)
│ ├── jwtx (JWT package)
│ ├── logging (Logging package)
│ ├── mail (Mail package)
│ ├── middleware (Middleware package)
│ ├── oss (Object storage package)
│ ├── promx (Prometheus package)
│ └── util (Utility package)
├── test (Unit test directory)
│ ├── menu_test.go
│ ├── role_test.go
│ ├── test.go
│ └── user_test.go
├── Dockerfile
├── Makefile
├── README.md
├── go.mod
├── go.sum
└── main.go (Entry file)
```
## License
Copyright (c) 2023 Lyric
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

105
cmd/start.go Normal file
View File

@ -0,0 +1,105 @@
package cmd
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/guxuan/hailin_service/internal/bootstrap"
"github.com/guxuan/hailin_service/internal/config"
"github.com/urfave/cli/v2"
)
// The function defines a CLI command to start a server with various flags and options, including the
// ability to run as a daemon.
func StartCmd() *cli.Command {
return &cli.Command{
Name: "start",
Usage: "Start server",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "workdir",
Aliases: []string{"d"},
Usage: "Working directory",
DefaultText: "configs",
Value: "configs",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Runtime configuration files or directory (relative to workdir, multiple separated by commas)",
DefaultText: "dev",
Value: "dev",
},
&cli.StringFlag{
Name: "static",
Aliases: []string{"s"},
Usage: "Static files directory",
},
&cli.BoolFlag{
Name: "daemon",
Usage: "Run as a daemon",
},
},
Action: func(c *cli.Context) error {
workDir := c.String("workdir")
staticDir := c.String("static")
configs := c.String("config")
if c.Bool("daemon") {
bin, err := filepath.Abs(os.Args[0])
if err != nil {
fmt.Printf("failed to get absolute path for command: %s \n", err.Error())
return err
}
args := []string{"start"}
args = append(args, "-d", workDir)
args = append(args, "-c", configs)
args = append(args, "-s", staticDir)
fmt.Printf("execute command: %s %s \n", bin, strings.Join(args, " "))
command := exec.Command(bin, args...)
// Redirect stdout and stderr to log file
stdLogFile := fmt.Sprintf("%s.log", c.App.Name)
file, err := os.OpenFile(stdLogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
fmt.Printf("failed to open log file: %s \n", err.Error())
return err
}
defer file.Close()
command.Stdout = file
command.Stderr = file
err = command.Start()
if err != nil {
fmt.Printf("failed to start daemon thread: %s \n", err.Error())
return err
}
// Don't wait for the command to finish
// The main process will exit, allowing the daemon to run independently
fmt.Printf("Service %s daemon thread started successfully\n", config.C.General.AppName)
pid := command.Process.Pid
_ = os.WriteFile(fmt.Sprintf("%s.lock", c.App.Name), []byte(fmt.Sprintf("%d", pid)), 0666)
fmt.Printf("service %s daemon thread started with pid %d \n", config.C.General.AppName, pid)
os.Exit(0)
}
err := bootstrap.Run(context.Background(), bootstrap.RunConfig{
WorkDir: workDir,
Configs: configs,
StaticDir: staticDir,
})
if err != nil {
panic(err)
}
return nil
},
}
}

40
cmd/stop.go Normal file
View File

@ -0,0 +1,40 @@
package cmd
import (
"fmt"
"os"
"os/exec"
"github.com/urfave/cli/v2"
)
// The function defines a CLI command to stop a server by reading a lock file, killing the process with
// the corresponding PID, and removing the lock file.
func StopCmd() *cli.Command {
return &cli.Command{
Name: "stop",
Usage: "stop server",
Action: func(c *cli.Context) error {
appName := c.App.Name
lockFile := fmt.Sprintf("%s.lock", appName)
pid, err := os.ReadFile(lockFile)
if err != nil {
return err
}
command := exec.Command("kill", string(pid))
err = command.Start()
if err != nil {
return err
}
err = os.Remove(lockFile)
if err != nil {
return fmt.Errorf("can't remove %s.lock. %s", appName, err.Error())
}
fmt.Printf("service %s stopped \n", appName)
return nil
},
}
}

19
cmd/version.go Normal file
View File

@ -0,0 +1,19 @@
package cmd
import (
"fmt"
"github.com/urfave/cli/v2"
)
// This function creates a CLI command that prints the version number.
func VersionCmd(v string) *cli.Command {
return &cli.Command{
Name: "version",
Usage: "Show version",
Action: func(_ *cli.Context) error {
fmt.Println(v)
return nil
},
}
}

26
configs/dev/logging.toml Normal file
View File

@ -0,0 +1,26 @@
[Logger]
Debug = true
Level = "debug" # debug/info/warn/error/dpanic/panic/fatal
CallerSkip = 1
[Logger.File]
Enable = false
Path = "data/log/hailinservice.log"
MaxBackups = 20 # Files
MaxSize = 64 # MB
[[Logger.Hooks]]
Enable = true
Level = "info"
Type = "gorm" # gorm
MaxBuffer = 1024
MaxThread = 2
[Logger.Hooks.Options]
Debug = "false"
DBType = "sqlite3" # sqlite3/mysql/postgres
DSN = "data/hailinservice.db"
MaxOpenConns = "16"
MaxIdleConns = "4"
MaxLifetime = "86400"
MaxIdleTime = "7200"

View File

@ -0,0 +1,76 @@
[Middleware]
[Middleware.Recovery]
Skip = 3
[Middleware.CORS]
Enable = true
AllowOrigins = ["*"]
AllowMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
AllowHeaders = ["*"]
MaxAge = 86400
AllowWildcard = true
AllowWebSockets = true
AllowFiles = true
[Middleware.Trace]
RequestHeaderKey = "X-Request-Id"
ResponseTraceKey = "X-Trace-Id"
[Middleware.Logger]
MaxOutputRequestBodyLen = 4096 # bytes
MaxOutputResponseBodyLen = 4096 # bytes
[Middleware.CopyBody]
MaxContentLen = 134217728 # 128MB
[Middleware.Auth]
Disable = true
SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login", "/api/v1/web/articles", "/api/v1/web/jobs"]
SigningMethod = "HS512" # HS256/HS384/HS512
SigningKey = "XnEsT0S@" # Secret key
OldSigningKey = "" # Old secret key (For change secret key)
Expired = 86400 # seconds
[Middleware.Auth.Store]
Type = "badger" # memory/badger/redis
Delimiter = ":"
[Middleware.Auth.Store.Memory]
CleanupInterval = 60 # seconds
[Middleware.Auth.Store.Badger]
Path = "data/auth"
[Middleware.Auth.Store.Redis]
Addr = "" # If empty, then use the same configuration as Storage.Cache.Redis
Username = ""
Password = ""
DB = 2
[Middleware.RateLimiter]
Enable = false
Period = 10 # seconds
MaxRequestsPerIP = 1000
MaxRequestsPerUser = 500
[Middleware.RateLimiter.Store]
Type = "memory" # memory/redis
[Middleware.RateLimiter.Store.Memory]
Expiration = 3600
CleanupInterval = 60
[Middleware.RateLimiter.Store.Redis]
Addr = "" # If empty, then use the same configuration as Storage.Cache.Redis
Username = ""
Password = ""
DB = 10
[Middleware.Casbin]
Disable = true
SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login", "/api/v1/current/", "/api/v1/web/articles", "/api/v1/web/jobs"]
LoadThread = 2
AutoLoadInterval = 3 # seconds
ModelFile = "rbac_model.conf"
GenPolicyFile = "gen_rbac_policy.csv"

89
configs/dev/server.toml Normal file
View File

@ -0,0 +1,89 @@
[General]
AppName = "hailinservice"
Version = "v10.1.0"
Debug = true
PprofAddr = "" # Pprof monitor address, "localhost:6060"
DisableSwagger = false
DisablePrintConfig = false
DefaultLoginPwd = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123")
MenuFile = "menu.json" # Or use "menu_cn.json"
DenyOperateMenu = false
[General.HTTP]
Addr = ":8060"
ShutdownTimeout = 10
ReadTimeout = 60
WriteTimeout = 60
IdleTimeout = 10
CertFile = ""
KeyFile = ""
[General.Root] # Super Administrator Account
ID = "root"
Username = "admin"
Password = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123")
Name = "Admin"
[Storage]
[Storage.Cache]
Type = "memory" # memory/badger/redis
Delimiter = ":"
[Storage.Cache.Memory]
CleanupInterval = 60
[Storage.Cache.Badger]
Path = "data/cache"
[Storage.Cache.Redis]
Addr = "127.0.0.1:6379"
Username = ""
Password = ""
DB = 1
[Storage.DB]
Debug = true
Type = "mysql" # sqlite3/mysql/postgres
# SQLite3 DSN
#DSN = "data/hailinservice.db"
# MySQL DSN
DSN = "hailin:z2askDphamsxk5BH@tcp(115.239.217.220:3306)/hailin?charset=utf8mb4&parseTime=True&loc=Local"
# PostgreSQL DSN
# DSN = "host=db user=postgres password=123456 dbname=hailinservice port=5432 sslmode=disable TimeZone=Asia/Shanghai"
MaxLifetime = 86400
MaxIdleTime = 3600
MaxOpenConns = 100
MaxIdleConns = 50
TablePrefix = ""
AutoMigrate = true
[Util]
[Util.Captcha]
Length = 4
Width = 400
Height = 160
CacheType = "memory" # memory/redis
[Util.Captcha.Redis]
Addr = "" # If empty, then use the same configuration as Storage.Cache.Redis
Username = ""
Password = ""
DB = 1
KeyPrefix = "captcha:"
[Util.Prometheus]
Enable = false
Port = 9100
BasicUsername = "admin"
BasicPassword = "admin"
LogApis = [] # Log APIs, e.g. ["/api/v1/users"]
LogMethods = [] # Log HTTP methods, e.g. ["GET"]
DefaultCollect = true
[Dictionary]
UserCacheExp = 4 # hours
[FileConfig]
UploadDir = "./uploads"
StaticPrefix = "/static"

240
configs/menu.json Normal file
View File

@ -0,0 +1,240 @@
[
{
"code": "home",
"name": "Home",
"sequence": 90,
"type": "page",
"path": "/home",
"status": "enabled"
},
{
"code": "system",
"name": "System",
"sequence": 10,
"type": "page",
"path": "/system",
"status": "enabled",
"children": [
{
"code": "menu",
"name": "Menu",
"sequence": 90,
"type": "page",
"path": "/system/menu",
"status": "enabled",
"children": [
{
"code": "add",
"name": "Add",
"sequence": 9,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "POST",
"path": "/api/v1/menus"
}
]
},
{
"code": "edit",
"name": "Edit",
"sequence": 8,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "PUT",
"path": "/api/v1/menus/{id}"
}
]
},
{
"code": "delete",
"name": "Delete",
"sequence": 7,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "DELETE",
"path": "/api/v1/menus/{id}"
}
]
},
{
"code": "search",
"name": "Search",
"sequence": 6,
"type": "button",
"status": "enabled"
}
],
"resources": [
{
"method": "GET",
"path": "/api/v1/menus"
},
{
"method": "GET",
"path": "/api/v1/menus/{id}"
}
]
},
{
"code": "role",
"name": "Role",
"sequence": 80,
"type": "page",
"path": "/system/role",
"status": "enabled",
"children": [
{
"code": "add",
"name": "Add",
"sequence": 9,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "POST",
"path": "/api/v1/roles"
}
]
},
{
"code": "edit",
"name": "Edit",
"sequence": 8,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "PUT",
"path": "/api/v1/roles/{id}"
}
]
},
{
"code": "delete",
"name": "Delete",
"sequence": 7,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "DELETE",
"path": "/api/v1/roles/{id}"
}
]
},
{
"code": "search",
"name": "Search",
"sequence": 6,
"type": "button",
"status": "enabled"
}
],
"resources": [
{
"method": "GET",
"path": "/api/v1/menus"
},
{
"method": "GET",
"path": "/api/v1/roles"
},
{
"method": "GET",
"path": "/api/v1/roles/{id}"
}
]
},
{
"code": "user",
"name": "User",
"sequence": 70,
"type": "page",
"path": "/system/user",
"status": "enabled",
"children": [
{
"code": "add",
"name": "Add",
"sequence": 9,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "POST",
"path": "/api/v1/users"
}
]
},
{
"code": "edit",
"name": "Edit",
"sequence": 8,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "PUT",
"path": "/api/v1/users/{id}"
}
]
},
{
"code": "delete",
"name": "Delete",
"sequence": 7,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "DELETE",
"path": "/api/v1/users/{id}"
}
]
},
{
"code": "search",
"name": "Search",
"sequence": 6,
"type": "button",
"status": "enabled"
}
],
"resources": [
{
"method": "GET",
"path": "/api/v1/roles"
},
{
"method": "GET",
"path": "/api/v1/users"
},
{
"method": "GET",
"path": "/api/v1/users/{id}"
}
]
},
{
"code": "logger",
"name": "Logger",
"sequence": 10,
"type": "page",
"path": "/system/logger",
"status": "enabled",
"resources": [
{
"method": "GET",
"path": "/api/v1/loggers"
}
]
}
]
}
]

240
configs/menu_cn.json Normal file
View File

@ -0,0 +1,240 @@
[
{
"code": "home",
"name": "首页",
"sequence": 90,
"type": "page",
"path": "/home",
"status": "enabled"
},
{
"code": "system",
"name": "系统管理",
"sequence": 10,
"type": "page",
"path": "/system",
"status": "enabled",
"children": [
{
"code": "menu",
"name": "菜单管理",
"sequence": 90,
"type": "page",
"path": "/system/menu",
"status": "enabled",
"children": [
{
"code": "add",
"name": "增加",
"sequence": 9,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "POST",
"path": "/api/v1/menus"
}
]
},
{
"code": "edit",
"name": "编辑",
"sequence": 8,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "PUT",
"path": "/api/v1/menus/{id}"
}
]
},
{
"code": "delete",
"name": "删除",
"sequence": 7,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "DELETE",
"path": "/api/v1/menus/{id}"
}
]
},
{
"code": "search",
"name": "查询",
"sequence": 6,
"type": "button",
"status": "enabled"
}
],
"resources": [
{
"method": "GET",
"path": "/api/v1/menus"
},
{
"method": "GET",
"path": "/api/v1/menus/{id}"
}
]
},
{
"code": "role",
"name": "角色管理",
"sequence": 80,
"type": "page",
"path": "/system/role",
"status": "enabled",
"children": [
{
"code": "add",
"name": "增加",
"sequence": 9,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "POST",
"path": "/api/v1/roles"
}
]
},
{
"code": "edit",
"name": "编辑",
"sequence": 8,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "PUT",
"path": "/api/v1/roles/{id}"
}
]
},
{
"code": "delete",
"name": "删除",
"sequence": 7,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "DELETE",
"path": "/api/v1/roles/{id}"
}
]
},
{
"code": "search",
"name": "查询",
"sequence": 6,
"type": "button",
"status": "enabled"
}
],
"resources": [
{
"method": "GET",
"path": "/api/v1/menus"
},
{
"method": "GET",
"path": "/api/v1/roles"
},
{
"method": "GET",
"path": "/api/v1/roles/{id}"
}
]
},
{
"code": "user",
"name": "用户管理",
"sequence": 70,
"type": "page",
"path": "/system/user",
"status": "enabled",
"children": [
{
"code": "add",
"name": "增加",
"sequence": 9,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "POST",
"path": "/api/v1/users"
}
]
},
{
"code": "edit",
"name": "编辑",
"sequence": 8,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "PUT",
"path": "/api/v1/users/{id}"
}
]
},
{
"code": "delete",
"name": "删除",
"sequence": 7,
"type": "button",
"status": "enabled",
"resources": [
{
"method": "DELETE",
"path": "/api/v1/users/{id}"
}
]
},
{
"code": "search",
"name": "查询",
"sequence": 6,
"type": "button",
"status": "enabled"
}
],
"resources": [
{
"method": "GET",
"path": "/api/v1/roles"
},
{
"method": "GET",
"path": "/api/v1/users"
},
{
"method": "GET",
"path": "/api/v1/users/{id}"
}
]
},
{
"code": "logger",
"name": "日志查询",
"sequence": 10,
"type": "page",
"path": "/system/logger",
"status": "enabled",
"resources": [
{
"method": "GET",
"path": "/api/v1/loggers"
}
]
}
]
}
]

14
configs/rbac_model.conf Normal file
View File

@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow)) # Passes auth if any of the policies allows
[role_definition]
g = _, _
[matchers]
m = g(r.sub, p.sub) && r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && r.act == p.act

114
go.mod Normal file
View File

@ -0,0 +1,114 @@
module github.com/guxuan/hailin_service
go 1.19
require (
github.com/BurntSushi/toml v1.2.1
github.com/LyricTian/captcha v1.2.0
github.com/aws/aws-sdk-go v1.44.300
github.com/casbin/casbin/v2 v2.68.0
github.com/creasty/defaults v1.7.0
github.com/dgraph-io/badger/v3 v3.2103.5
github.com/gin-contrib/cors v1.4.0
github.com/gin-gonic/gin v1.9.0
github.com/go-playground/validator/v10 v10.12.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-redis/redis_rate/v9 v9.1.2
github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.3.0
github.com/google/wire v0.5.0
github.com/json-iterator/go v1.1.12
github.com/minio/minio-go/v7 v7.0.51
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pelletier/go-toml v1.9.5
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.14.0
github.com/redis/go-redis/v9 v9.0.4
github.com/rs/xid v1.4.0
github.com/spf13/cast v1.5.1
github.com/stretchr/testify v1.8.4
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.2
github.com/urfave/cli/v2 v2.25.1
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.8.0
golang.org/x/time v0.3.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.4.7
gorm.io/driver/postgres v1.5.0
gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11
gorm.io/plugin/dbresolver v1.4.1
)
require (
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.8.7 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.8 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v23.3.3+incompatible // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.3 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.0 // 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.0.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.8.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

528
go.sum Normal file
View File

@ -0,0 +1,528 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/LyricTian/captcha v1.2.0 h1:SXmXj9B1KHBTsVv9rXVVTsKdgNlHrSeb4Memlw+wks4=
github.com/LyricTian/captcha v1.2.0/go.mod h1:tpNDvMWf9XBnkfPB2Tyx7lxDWTgfZ9wA4o7Yl50FWs4=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.44.300 h1:Zn+3lqgYahIf9yfrwZ+g+hq/c3KzUBaQ8wqY/ZXiAbY=
github.com/aws/aws-sdk-go v1.44.300/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/casbin/casbin/v2 v2.68.0 h1:7L4kwNJJw/pzdSEhl4SkeHz+1JzYn8guO+Q422sxzLM=
github.com/casbin/casbin/v2 v2.68.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
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/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU=
github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
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.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
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.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redis/redis_rate/v9 v9.1.2 h1:H0l5VzoAtOE6ydd38j8MCq3ABlGLnvvbA1xDSVVCHgQ=
github.com/go-redis/redis_rate/v9 v9.1.2/go.mod h1:oam2de2apSgRG8aJzwJddXbNu91Iyz1m8IKJE2vpvlQ=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v23.3.3+incompatible h1:5PJI/WbJkaMTvpGxsHVKG/LurN/KnWXNyGpwSCDgen0=
github.com/google/flatbuffers v23.3.3+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
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.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.51 h1:eSewrwc23TqUDEH8aw8Bwp4f+JDdozRrPWcKR7DZhmY=
github.com/minio/minio-go/v7 v7.0.51/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
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 v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
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.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
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.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 h1:9qNbmu21nNThCNnF5i2R3kw2aL27U8ZwbzccNjOmW0g=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk=
gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -0,0 +1,106 @@
package bootstrap
import (
"context"
"fmt"
"net/http"
_ "net/http/pprof" //nolint:gosec
"os"
"strings"
"github.com/guxuan/hailin_service/internal/config"
_ "github.com/guxuan/hailin_service/internal/swagger"
"github.com/guxuan/hailin_service/internal/utility/prom"
"github.com/guxuan/hailin_service/internal/wirex"
"github.com/guxuan/hailin_service/pkg/logging"
"github.com/guxuan/hailin_service/pkg/util"
"go.uber.org/zap"
)
// RunConfig defines the config for run command.
type RunConfig struct {
WorkDir string // Working directory
Configs string // Directory or files (multiple separated by commas)
StaticDir string // Static files directory
}
// The Run function initializes and starts a service with configuration and logging, and handles
// cleanup upon exit.
func Run(ctx context.Context, runCfg RunConfig) error {
defer func() {
if err := zap.L().Sync(); err != nil {
fmt.Printf("failed to sync zap logger: %s \n", err.Error())
}
}()
// Load configuration.
workDir := runCfg.WorkDir
staticDir := runCfg.StaticDir
config.MustLoad(workDir, strings.Split(runCfg.Configs, ",")...)
config.C.General.WorkDir = workDir
config.C.Middleware.Static.Dir = staticDir
config.C.Print()
config.C.PreLoad()
// Initialize logger.
cleanLoggerFn, err := logging.InitWithConfig(ctx, &config.C.Logger, initLoggerHook)
if err != nil {
return err
}
ctx = logging.NewTag(ctx, logging.TagKeyMain)
logging.Context(ctx).Info("starting service ...",
zap.String("version", config.C.General.Version),
zap.Int("pid", os.Getpid()),
zap.String("workdir", workDir),
zap.String("config", runCfg.Configs),
zap.String("static", staticDir),
)
// Start pprof server.
if addr := config.C.General.PprofAddr; addr != "" {
logging.Context(ctx).Info("pprof server is listening on " + addr)
go func() {
err := http.ListenAndServe(addr, nil)
if err != nil {
logging.Context(ctx).Error("failed to listen pprof server", zap.Error(err))
}
}()
}
// Build injector.
injector, cleanInjectorFn, err := wirex.BuildInjector(ctx)
if err != nil {
return err
}
if err := injector.M.Init(ctx); err != nil {
return err
}
// Initialize global prometheus metrics.
prom.Init()
return util.Run(ctx, func(ctx context.Context) (func(), error) {
cleanHTTPServerFn, err := startHTTPServer(ctx, injector)
if err != nil {
return cleanInjectorFn, err
}
return func() {
if err := injector.M.Release(ctx); err != nil {
logging.Context(ctx).Error("failed to release injector", zap.Error(err))
}
if cleanHTTPServerFn != nil {
cleanHTTPServerFn()
}
if cleanInjectorFn != nil {
cleanInjectorFn()
}
if cleanLoggerFn != nil {
cleanLoggerFn()
}
}, nil
})
}

192
internal/bootstrap/http.go Normal file
View File

@ -0,0 +1,192 @@
package bootstrap
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"path/filepath"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/utility/prom"
"github.com/guxuan/hailin_service/internal/wirex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/logging"
"github.com/guxuan/hailin_service/pkg/middleware"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/casbin/casbin/v2"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"go.uber.org/zap"
)
func startHTTPServer(ctx context.Context, injector *wirex.Injector) (func(), error) {
if config.C.IsDebug() {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
e := gin.New()
e.GET("/health", func(c *gin.Context) {
util.ResOK(c)
})
e.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{
Skip: config.C.Middleware.Recovery.Skip,
}))
e.NoMethod(func(c *gin.Context) {
util.ResError(c, errors.MethodNotAllowed("", "Method Not Allowed"))
})
e.NoRoute(func(c *gin.Context) {
util.ResError(c, errors.NotFound("", "Not Found"))
})
allowedPrefixes := injector.M.RouterPrefixes()
// Register middlewares
if err := useHTTPMiddlewares(ctx, e, injector, allowedPrefixes); err != nil {
return nil, err
}
// Register routers
if err := injector.M.RegisterRouters(ctx, e); err != nil {
return nil, err
}
// Register swagger
if !config.C.General.DisableSwagger {
e.StaticFile("/openapi.json", filepath.Join(config.C.General.WorkDir, "openapi.json"))
e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
if dir := config.C.Middleware.Static.Dir; dir != "" {
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
Root: dir,
SkippedPathPrefixes: allowedPrefixes,
}))
}
addr := config.C.General.HTTP.Addr
logging.Context(ctx).Info(fmt.Sprintf("HTTP server is listening on %s", addr))
srv := &http.Server{
Addr: addr,
Handler: e,
ReadTimeout: time.Second * time.Duration(config.C.General.HTTP.ReadTimeout),
WriteTimeout: time.Second * time.Duration(config.C.General.HTTP.WriteTimeout),
IdleTimeout: time.Second * time.Duration(config.C.General.HTTP.IdleTimeout),
}
go func() {
var err error
if config.C.General.HTTP.CertFile != "" && config.C.General.HTTP.KeyFile != "" {
srv.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12}
err = srv.ListenAndServeTLS(config.C.General.HTTP.CertFile, config.C.General.HTTP.KeyFile)
} else {
err = srv.ListenAndServe()
}
if err != nil && err != http.ErrServerClosed {
logging.Context(ctx).Error("Failed to listen http server", zap.Error(err))
}
}()
return func() {
ctx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(config.C.General.HTTP.ShutdownTimeout))
defer cancel()
srv.SetKeepAlivesEnabled(false)
if err := srv.Shutdown(ctx); err != nil {
logging.Context(ctx).Error("Failed to shutdown http server", zap.Error(err))
}
}, nil
}
func useHTTPMiddlewares(_ context.Context, e *gin.Engine, injector *wirex.Injector, allowedPrefixes []string) error {
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
Enable: config.C.Middleware.CORS.Enable,
AllowAllOrigins: config.C.Middleware.CORS.AllowAllOrigins,
AllowOrigins: config.C.Middleware.CORS.AllowOrigins,
AllowMethods: config.C.Middleware.CORS.AllowMethods,
AllowHeaders: config.C.Middleware.CORS.AllowHeaders,
AllowCredentials: config.C.Middleware.CORS.AllowCredentials,
ExposeHeaders: config.C.Middleware.CORS.ExposeHeaders,
MaxAge: config.C.Middleware.CORS.MaxAge,
AllowWildcard: config.C.Middleware.CORS.AllowWildcard,
AllowBrowserExtensions: config.C.Middleware.CORS.AllowBrowserExtensions,
AllowWebSockets: config.C.Middleware.CORS.AllowWebSockets,
AllowFiles: config.C.Middleware.CORS.AllowFiles,
}))
e.Use(middleware.TraceWithConfig(middleware.TraceConfig{
AllowedPathPrefixes: allowedPrefixes,
SkippedPathPrefixes: config.C.Middleware.Trace.SkippedPathPrefixes,
RequestHeaderKey: config.C.Middleware.Trace.RequestHeaderKey,
ResponseTraceKey: config.C.Middleware.Trace.ResponseTraceKey,
}))
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
AllowedPathPrefixes: allowedPrefixes,
SkippedPathPrefixes: config.C.Middleware.Logger.SkippedPathPrefixes,
MaxOutputRequestBodyLen: config.C.Middleware.Logger.MaxOutputRequestBodyLen,
MaxOutputResponseBodyLen: config.C.Middleware.Logger.MaxOutputResponseBodyLen,
}))
e.Use(middleware.CopyBodyWithConfig(middleware.CopyBodyConfig{
AllowedPathPrefixes: allowedPrefixes,
SkippedPathPrefixes: config.C.Middleware.CopyBody.SkippedPathPrefixes,
MaxContentLen: config.C.Middleware.CopyBody.MaxContentLen,
}))
e.Use(middleware.AuthWithConfig(middleware.AuthConfig{
AllowedPathPrefixes: allowedPrefixes,
SkippedPathPrefixes: config.C.Middleware.Auth.SkippedPathPrefixes,
ParseUserID: injector.M.RBAC.LoginAPI.LoginBIZ.ParseUserID,
RootID: config.C.General.Root.ID,
}))
e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Enable: config.C.Middleware.RateLimiter.Enable,
AllowedPathPrefixes: allowedPrefixes,
SkippedPathPrefixes: config.C.Middleware.RateLimiter.SkippedPathPrefixes,
Period: config.C.Middleware.RateLimiter.Period,
MaxRequestsPerIP: config.C.Middleware.RateLimiter.MaxRequestsPerIP,
MaxRequestsPerUser: config.C.Middleware.RateLimiter.MaxRequestsPerUser,
StoreType: config.C.Middleware.RateLimiter.Store.Type,
MemoryStoreConfig: middleware.RateLimiterMemoryConfig{
Expiration: time.Second * time.Duration(config.C.Middleware.RateLimiter.Store.Memory.Expiration),
CleanupInterval: time.Second * time.Duration(config.C.Middleware.RateLimiter.Store.Memory.CleanupInterval),
},
RedisStoreConfig: middleware.RateLimiterRedisConfig{
Addr: config.C.Middleware.RateLimiter.Store.Redis.Addr,
Password: config.C.Middleware.RateLimiter.Store.Redis.Password,
DB: config.C.Middleware.RateLimiter.Store.Redis.DB,
Username: config.C.Middleware.RateLimiter.Store.Redis.Username,
},
}))
e.Use(middleware.CasbinWithConfig(middleware.CasbinConfig{
AllowedPathPrefixes: allowedPrefixes,
SkippedPathPrefixes: config.C.Middleware.Casbin.SkippedPathPrefixes,
Skipper: func(c *gin.Context) bool {
if config.C.Middleware.Casbin.Disable ||
util.FromIsRootUser(c.Request.Context()) {
return true
}
return false
},
GetEnforcer: func(c *gin.Context) *casbin.Enforcer {
return injector.M.RBAC.Casbinx.GetEnforcer()
},
GetSubjects: func(c *gin.Context) []string {
return util.FromUserCache(c.Request.Context()).RoleIDs
},
}))
if config.C.Util.Prometheus.Enable {
e.Use(prom.GinMiddleware)
}
return nil
}

View File

@ -0,0 +1,43 @@
package bootstrap
import (
"context"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/gormx"
"github.com/guxuan/hailin_service/pkg/logging"
"github.com/spf13/cast"
)
func initLoggerHook(_ context.Context, cfg *logging.HookConfig) (*logging.Hook, error) {
extra := cfg.Extra
if extra == nil {
extra = make(map[string]string)
}
extra["appname"] = config.C.General.AppName
switch cfg.Type {
case "gorm":
db, err := gormx.New(gormx.Config{
Debug: cast.ToBool(cfg.Options["Debug"]),
DBType: cast.ToString(cfg.Options["DBType"]),
DSN: cast.ToString(cfg.Options["DSN"]),
MaxLifetime: cast.ToInt(cfg.Options["MaxLifetime"]),
MaxIdleTime: cast.ToInt(cfg.Options["MaxIdleTime"]),
MaxOpenConns: cast.ToInt(cfg.Options["MaxOpenConns"]),
MaxIdleConns: cast.ToInt(cfg.Options["MaxIdleConns"]),
TablePrefix: config.C.Storage.DB.TablePrefix,
})
if err != nil {
return nil, err
}
hook := logging.NewHook(logging.NewGormHook(db),
logging.SetHookExtra(cfg.Extra),
logging.SetHookMaxJobs(cfg.MaxBuffer),
logging.SetHookMaxWorkers(cfg.MaxThread))
return hook, nil
default:
return nil, nil
}
}

166
internal/config/config.go Normal file
View File

@ -0,0 +1,166 @@
package config
import (
"fmt"
"github.com/guxuan/hailin_service/pkg/encoding/json"
"github.com/guxuan/hailin_service/pkg/logging"
)
type Config struct {
Logger logging.LoggerConfig
General General
Storage Storage
Middleware Middleware
Util Util
Dictionary Dictionary
FileConfig FileConfig
}
type FileConfig struct {
UploadDir string
StaticPrefix string
}
type General struct {
AppName string `default:"hailinservice"`
Version string `default:"v1.0.0"`
Debug bool
PprofAddr string
DisableSwagger bool
DisablePrintConfig bool
DefaultLoginPwd string `default:"6351623c8cef86fefabfa7da046fc619"` // MD5(abc-123)
WorkDir string // From command arguments
MenuFile string // From schema.Menus (JSON/YAML)
DenyOperateMenu bool
HTTP struct {
Addr string `default:":8040"`
ShutdownTimeout int `default:"10"` // seconds
ReadTimeout int `default:"60"` // seconds
WriteTimeout int `default:"60"` // seconds
IdleTimeout int `default:"10"` // seconds
CertFile string
KeyFile string
}
Root struct {
ID string `default:"root"`
Username string `default:"admin"`
Password string
Name string `default:"Admin"`
}
}
type Storage struct {
Cache struct {
Type string `default:"memory"` // memory/badger/redis
Delimiter string `default:":"` // delimiter for key
Memory struct {
CleanupInterval int `default:"60"` // seconds
}
Badger struct {
Path string `default:"data/cache"`
}
Redis struct {
Addr string
Username string
Password string
DB int
}
}
DB struct {
Debug bool
Type string `default:"sqlite3"` // sqlite3/mysql/postgres
DSN string `default:"data/hailinservice.db"` // database source name
MaxLifetime int `default:"86400"` // seconds
MaxIdleTime int `default:"3600"` // seconds
MaxOpenConns int `default:"100"` // connections
MaxIdleConns int `default:"50"` // connections
TablePrefix string `default:""`
AutoMigrate bool
PrepareStmt bool
Resolver []struct {
DBType string // sqlite3/mysql/postgres
Sources []string // DSN
Replicas []string // DSN
Tables []string
}
}
}
type Util struct {
Captcha struct {
Length int `default:"4"`
Width int `default:"400"`
Height int `default:"160"`
CacheType string `default:"memory"` // memory/redis
Redis struct {
Addr string
Username string
Password string
DB int
KeyPrefix string `default:"captcha:"`
}
}
Prometheus struct {
Enable bool
Port int `default:"9100"`
BasicUsername string `default:"admin"`
BasicPassword string `default:"admin"`
LogApis []string
LogMethods []string
DefaultCollect bool
}
}
type Dictionary struct {
UserCacheExp int `default:"4"` // hours
}
func (c *Config) IsDebug() bool {
return c.General.Debug
}
func (c *Config) String() string {
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
panic("Failed to marshal config: " + err.Error())
}
return string(b)
}
func (c *Config) PreLoad() {
if addr := c.Storage.Cache.Redis.Addr; addr != "" {
username := c.Storage.Cache.Redis.Username
password := c.Storage.Cache.Redis.Password
if c.Util.Captcha.CacheType == "redis" &&
c.Util.Captcha.Redis.Addr == "" {
c.Util.Captcha.Redis.Addr = addr
c.Util.Captcha.Redis.Username = username
c.Util.Captcha.Redis.Password = password
}
if c.Middleware.RateLimiter.Store.Type == "redis" &&
c.Middleware.RateLimiter.Store.Redis.Addr == "" {
c.Middleware.RateLimiter.Store.Redis.Addr = addr
c.Middleware.RateLimiter.Store.Redis.Username = username
c.Middleware.RateLimiter.Store.Redis.Password = password
}
if c.Middleware.Auth.Store.Type == "redis" &&
c.Middleware.Auth.Store.Redis.Addr == "" {
c.Middleware.Auth.Store.Redis.Addr = addr
c.Middleware.Auth.Store.Redis.Username = username
c.Middleware.Auth.Store.Redis.Password = password
}
}
}
func (c *Config) Print() {
if c.General.DisablePrintConfig {
return
}
fmt.Println("// ----------------------- Load configurations start ------------------------")
fmt.Println(c.String())
fmt.Println("// ----------------------- Load configurations end --------------------------")
}
func (c *Config) FormatTableName(name string) string {
return c.Storage.DB.TablePrefix + name
}

16
internal/config/consts.go Normal file
View File

@ -0,0 +1,16 @@
package config
const (
CacheNSForUser = "user"
CacheNSForRole = "role"
)
const (
CacheKeyForSyncToCasbin = "sync:casbin"
)
const (
ErrInvalidTokenID = "com.invalid.token"
ErrInvalidCaptchaID = "com.invalid.captcha"
ErrInvalidUsernameOrPassword = "com.invalid.username-or-password"
)

View File

@ -0,0 +1,90 @@
package config
type Middleware struct {
Recovery struct {
Skip int `default:"3"` // skip the first n stack frames
}
CORS struct {
Enable bool
AllowAllOrigins bool
AllowOrigins []string
AllowMethods []string
AllowHeaders []string
AllowCredentials bool
ExposeHeaders []string
MaxAge int
AllowWildcard bool
AllowBrowserExtensions bool
AllowWebSockets bool
AllowFiles bool
}
Trace struct {
SkippedPathPrefixes []string
RequestHeaderKey string `default:"X-Request-Id"`
ResponseTraceKey string `default:"X-Trace-Id"`
}
Logger struct {
SkippedPathPrefixes []string
MaxOutputRequestBodyLen int `default:"4096"`
MaxOutputResponseBodyLen int `default:"1024"`
}
CopyBody struct {
SkippedPathPrefixes []string
MaxContentLen int64 `default:"33554432"` // max content length (default 32MB)
}
Auth struct {
Disable bool
SkippedPathPrefixes []string
SigningMethod string `default:"HS512"` // HS256/HS384/HS512
SigningKey string `default:"XnEsT0S@"` // secret key
OldSigningKey string // old secret key (for migration)
Expired int `default:"86400"` // seconds
Store struct {
Type string `default:"memory"` // memory/badger/redis
Delimiter string `default:":"` // delimiter for key
Memory struct {
CleanupInterval int `default:"60"` // seconds
}
Badger struct {
Path string `default:"data/auth"`
}
Redis struct {
Addr string
Username string
Password string
DB int
}
}
}
RateLimiter struct {
Enable bool
SkippedPathPrefixes []string
Period int // seconds
MaxRequestsPerIP int
MaxRequestsPerUser int
Store struct {
Type string // memory/redis
Memory struct {
Expiration int `default:"3600"` // seconds
CleanupInterval int `default:"60"` // seconds
}
Redis struct {
Addr string
Username string
Password string
DB int
}
}
}
Casbin struct {
Disable bool
SkippedPathPrefixes []string
LoadThread int `default:"2"`
AutoLoadInterval int `default:"3"` // seconds
ModelFile string `default:"rbac_model.conf"`
GenPolicyFile string `default:"gen_rbac_policy.csv"`
}
Static struct {
Dir string // Static files directory (From command arguments)
}
}

84
internal/config/parse.go Normal file
View File

@ -0,0 +1,84 @@
package config
import (
"os"
"path/filepath"
"strings"
"sync"
"github.com/guxuan/hailin_service/pkg/encoding/json"
"github.com/guxuan/hailin_service/pkg/encoding/toml"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/creasty/defaults"
)
var (
once sync.Once
C = new(Config)
)
func MustLoad(dir string, names ...string) {
once.Do(func() {
if err := Load(dir, names...); err != nil {
panic(err)
}
})
}
// Loads configuration files in various formats from a directory and parses them into
// a struct.
func Load(dir string, names ...string) error {
// Set default values
if err := defaults.Set(C); err != nil {
return err
}
supportExts := []string{".json", ".toml"}
parseFile := func(name string) error {
ext := filepath.Ext(name)
if ext == "" || !strings.Contains(strings.Join(supportExts, ","), ext) {
return nil
}
buf, err := os.ReadFile(name)
if err != nil {
return errors.Wrapf(err, "failed to read config file %s", name)
}
switch ext {
case ".json":
err = json.Unmarshal(buf, C)
case ".toml":
err = toml.Unmarshal(buf, C)
}
return errors.Wrapf(err, "failed to unmarshal config %s", name)
}
for _, name := range names {
fullname := filepath.Join(dir, name)
info, err := os.Stat(fullname)
if err != nil {
return errors.Wrapf(err, "failed to get config file %s", name)
}
if info.IsDir() {
err := filepath.WalkDir(fullname, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
} else if d.IsDir() {
return nil
}
return parseFile(path)
})
if err != nil {
return errors.Wrapf(err, "failed to walk config dir %s", name)
}
continue
}
if err := parseFile(fullname); err != nil {
return err
}
}
return nil
}

59
internal/mods/mods.go Normal file
View File

@ -0,0 +1,59 @@
package mods
import (
"context"
"github.com/guxuan/hailin_service/internal/config"
"github.com/gin-gonic/gin"
"github.com/google/wire"
"github.com/guxuan/hailin_service/internal/mods/rbac"
)
const (
apiPrefix = "/api/"
)
// Collection of wire providers
var Set = wire.NewSet(
wire.Struct(new(Mods), "*"),
rbac.Set,
)
type Mods struct {
RBAC *rbac.RBAC
}
func (a *Mods) Init(ctx context.Context) error {
if err := a.RBAC.Init(ctx); err != nil {
return err
}
return nil
}
func (a *Mods) RouterPrefixes() []string {
return []string{
apiPrefix,
}
}
func (a *Mods) RegisterRouters(ctx context.Context, e *gin.Engine) error {
gAPI := e.Group(apiPrefix)
e.Static(config.C.FileConfig.StaticPrefix, config.C.FileConfig.UploadDir)
v1 := gAPI.Group("v1")
if err := a.RBAC.RegisterV1Routers(ctx, v1); err != nil {
return err
}
return nil
}
func (a *Mods) Release(ctx context.Context) error {
if err := a.RBAC.Release(ctx); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,127 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
// Banner management for RBAC
type Article struct {
ArticleBIZ *biz.Article
}
// @Tags ArticleAPI
// @Security ApiKeyAuth
// @Summary Query Article list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param name query string false "name of Article"
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Article}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/articles [get]
func (a *Article) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.ArticleQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.ArticleBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags ArticleAPI
// @Security ApiKeyAuth
// @Summary Get Article record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Article}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/articles/{id} [get]
func (a *Article) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.ArticleBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags ArticleAPI
// @Security ApiKeyAuth
// @Summary Create Article record
// @Param body body schema.ArticleForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Article}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/articles [post]
func (a *Article) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.ArticleForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.ArticleBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags ArticleAPI
// @Security ApiKeyAuth
// @Summary Update Article record by ID
// @Param id path string true "unique id"
// @Param body body schema.ArticleForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/articles/{id} [put]
func (a *Article) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.ArticleForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.ArticleBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags ArticleAPI
// @Security ApiKeyAuth
// @Summary Delete Article record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/articles/{id} [delete]
func (a *Article) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.ArticleBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,127 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
// Banner management for RBAC
type Banner struct {
BannerBIZ *biz.Banner
}
// @Tags BannerAPI
// @Security ApiKeyAuth
// @Summary Query banner list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param name query string false "Name of banner"
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Banner}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/banners [get]
func (a *Banner) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.BannerQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.BannerBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags BannerAPI
// @Security ApiKeyAuth
// @Summary Get banner record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Banner}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/banners/{id} [get]
func (a *Banner) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.BannerBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags BannerAPI
// @Security ApiKeyAuth
// @Summary Create banner record
// @Param body body schema.BannerForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Banner}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/banners [post]
func (a *Banner) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.BannerForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.BannerBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags BannerAPI
// @Security ApiKeyAuth
// @Summary Update banner record by ID
// @Param id path string true "unique id"
// @Param body body schema.BannerForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/banners/{id} [put]
func (a *Banner) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.BannerForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.BannerBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags BannerAPI
// @Security ApiKeyAuth
// @Summary Delete banner record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/banners/{id} [delete]
func (a *Banner) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.BannerBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,244 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/resp"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
type Job struct {
JobBIZ *biz.Job
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Query Job list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param title query string false "title of Job"
// @Param jobAreaId query string false "jobAreaId of Job"
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Job}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs [get]
func (a *Job) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.JobQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.JobBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
list := result.Data
var newList []resp.JobAdminListResp
areas, err := a.JobBIZ.QueryJobArea(ctx)
if err != nil {
util.ResError(c, err)
return
}
for _, item := range list {
var newitem resp.JobAdminListResp
newitem.ID = item.ID
newitem.Status = item.Status
newitem.JobAreaID = item.JobAreaID
newitem.Title = item.Title
newitem.Introduce = item.Introduce
newitem.Duty = item.Duty
newitem.Salary = item.Salary
newitem.Sequence = item.Sequence
newitem.CreatedAt = item.CreatedAt
newitem.UpdatedAt = item.UpdatedAt
for _, area := range *areas {
if item.JobAreaID == area.ID {
newitem.AreaName = area.Name
}
}
newList = append(newList, newitem)
}
util.ResPage(c, newList, result.PageResult)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Get Job record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Job}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/{id} [get]
func (a *Job) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.JobBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Create Job record
// @Param body body schema.JobForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Job}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs [post]
func (a *Job) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.JobForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.JobBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Update Job record by ID
// @Param id path string true "unique id"
// @Param body body schema.JobForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/{id} [put]
func (a *Job) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.JobForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.JobBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Delete Job record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/{id} [delete]
func (a *Job) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.JobBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Query JobArea list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.JobArea}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/job_areas [get]
func (a *Job) QueryJobArea(c *gin.Context) {
ctx := c.Request.Context()
result, err := a.JobBIZ.QueryJobArea(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Create JobArea record
// @Param body body schema.JobAreaForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.JobAreaForm}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/job_areas [post]
func (a *Job) CreateJobArea(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.JobAreaForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.JobBIZ.CreateJobArea(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Update JobArea record by ID
// @Param id path string true "unique id"
// @Param body body schema.JobAreaForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/job_areas/{id} [put]
func (a *Job) UpdateJobArea(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.JobAreaForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.JobBIZ.UpdateJobArea(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags 职位模块
// @Security ApiKeyAuth
// @Summary Delete JobArea record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/jobs/job_areas/{id} [delete]
func (a *Job) DeleteJobArea(c *gin.Context) {
ctx := c.Request.Context()
err := a.JobBIZ.DeleteJobArea(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,45 @@
package api
import (
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/gin-gonic/gin"
)
// Logger management
type Logger struct {
LoggerBIZ *biz.Logger
}
// @Tags LoggerAPI
// @Security ApiKeyAuth
// @Summary Query logger list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param level query string false "log level"
// @Param traceID query string false "trace ID"
// @Param userName query string false "user name"
// @Param tag query string false "log tag"
// @Param message query string false "log message"
// @Param startTime query string false "start time"
// @Param endTime query string false "end time"
// @Success 200 {object} util.ResponseResult{data=[]schema.Logger}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/loggers [get]
func (a *Logger) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.LoggerQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.LoggerBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}

View File

@ -0,0 +1,182 @@
package api
import (
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/gin-gonic/gin"
)
type Login struct {
LoginBIZ *biz.Login
}
// @Tags LoginAPI
// @Summary Get captcha ID
// @Success 200 {object} util.ResponseResult{data=schema.Captcha}
// @Router /api/v1/captcha/id [get]
func (a *Login) GetCaptcha(c *gin.Context) {
ctx := c.Request.Context()
data, err := a.LoginBIZ.GetCaptcha(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, data)
}
// @Tags LoginAPI
// @Summary Response captcha image
// @Param id query string true "Captcha ID"
// @Param reload query number false "Reload captcha image (reload=1)"
// @Produce image/png
// @Success 200 "Captcha image"
// @Failure 404 {object} util.ResponseResult
// @Router /api/v1/captcha/image [get]
func (a *Login) ResponseCaptcha(c *gin.Context) {
ctx := c.Request.Context()
err := a.LoginBIZ.ResponseCaptcha(ctx, c.Writer, c.Query("id"), c.Query("reload") == "1")
if err != nil {
util.ResError(c, err)
}
}
// @Tags LoginAPI
// @Summary Login system with username and password
// @Param body body schema.LoginForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.LoginToken}
// @Failure 400 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/login [post]
func (a *Login) Login(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.LoginForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
data, err := a.LoginBIZ.Login(ctx, item.Trim())
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, data)
}
// @Tags LoginAPI
// @Security ApiKeyAuth
// @Summary Logout system
// @Success 200 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/current/logout [post]
func (a *Login) Logout(c *gin.Context) {
ctx := c.Request.Context()
err := a.LoginBIZ.Logout(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags LoginAPI
// @Security ApiKeyAuth
// @Summary Refresh current access token
// @Success 200 {object} util.ResponseResult{data=schema.LoginToken}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/current/refresh-token [post]
func (a *Login) RefreshToken(c *gin.Context) {
ctx := c.Request.Context()
data, err := a.LoginBIZ.RefreshToken(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, data)
}
// @Tags LoginAPI
// @Security ApiKeyAuth
// @Summary Get current user info
// @Success 200 {object} util.ResponseResult{data=schema.User}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/current/user [get]
func (a *Login) GetUserInfo(c *gin.Context) {
ctx := c.Request.Context()
data, err := a.LoginBIZ.GetUserInfo(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, data)
}
// @Tags LoginAPI
// @Security ApiKeyAuth
// @Summary Change current user password
// @Param body body schema.UpdateLoginPassword true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/current/password [put]
func (a *Login) UpdatePassword(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.UpdateLoginPassword)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.LoginBIZ.UpdatePassword(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags LoginAPI
// @Security ApiKeyAuth
// @Summary Query current user menus based on the current user role
// @Success 200 {object} util.ResponseResult{data=[]schema.Menu}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/current/menus [get]
func (a *Login) QueryMenus(c *gin.Context) {
ctx := c.Request.Context()
data, err := a.LoginBIZ.QueryMenus(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, data)
}
// @Tags LoginAPI
// @Security ApiKeyAuth
// @Summary Update current user info
// @Param body body schema.UpdateCurrentUser true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/current/user [put]
func (a *Login) UpdateUser(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.UpdateCurrentUser)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.LoginBIZ.UpdateUser(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,128 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
type Memorabilia struct {
MemorabiliaBIZ *biz.Memorabilia
}
// @Tags 发展历程模块
// @Security ApiKeyAuth
// @Summary Query Memorabilia list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param title query string false "title of Memorabilia"
// @Param month query string false "month of Memorabilia"
// @Param year query string false "year of Memorabilia"
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Memorabilia}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/memorabilias [get]
func (a *Memorabilia) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.MemorabiliaQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.MemorabiliaBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags 发展历程模块
// @Security ApiKeyAuth
// @Summary Get Memorabilia record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Memorabilia}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/memorabilias/{id} [get]
func (a *Memorabilia) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.MemorabiliaBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags 发展历程模块
// @Security ApiKeyAuth
// @Summary Create Article record
// @Param body body schema.MemorabiliaForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Memorabilia}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/memorabilias [post]
func (a *Memorabilia) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.MemorabiliaForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.MemorabiliaBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 发展历程模块
// @Security ApiKeyAuth
// @Summary Update Article record by ID
// @Param id path string true "unique id"
// @Param body body schema.MemorabiliaForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/memorabilias/{id} [put]
func (a *Memorabilia) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.MemorabiliaForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.MemorabiliaBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags 发展历程模块
// @Security ApiKeyAuth
// @Summary Delete Article record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/memorabilias/{id} [delete]
func (a *Memorabilia) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.MemorabiliaBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,132 @@
package api
import (
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/gin-gonic/gin"
)
// Menu management for RBAC
type Menu struct {
MenuBIZ *biz.Menu
}
// @Tags MenuAPI
// @Security ApiKeyAuth
// @Summary Query menu tree data
// @Param code query string false "Code path of menu (like xxx.xxx.xxx)"
// @Param name query string false "Name of menu"
// @Param includeResources query bool false "Whether to include menu resources"
// @Success 200 {object} util.ResponseResult{data=[]schema.Menu}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/menus [get]
func (a *Menu) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.MenuQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.MenuBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags MenuAPI
// @Security ApiKeyAuth
// @Summary Get menu record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Menu}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/menus/{id} [get]
func (a *Menu) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.MenuBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags MenuAPI
// @Security ApiKeyAuth
// @Summary Create menu record
// @Param body body schema.MenuForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Menu}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/menus [post]
func (a *Menu) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.MenuForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
} else if err := item.Validate(); err != nil {
util.ResError(c, err)
return
}
result, err := a.MenuBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags MenuAPI
// @Security ApiKeyAuth
// @Summary Update menu record by ID
// @Param id path string true "unique id"
// @Param body body schema.MenuForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/menus/{id} [put]
func (a *Menu) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.MenuForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
} else if err := item.Validate(); err != nil {
util.ResError(c, err)
return
}
err := a.MenuBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags MenuAPI
// @Security ApiKeyAuth
// @Summary Delete menu record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/menus/{id} [delete]
func (a *Menu) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.MenuBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,178 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
type Product struct {
ProductBIZ *biz.Product
}
// @Tags 产品模块
// @Security ApiKeyAuth
// @Summary Query Product list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param title query string false "title of Product"
// @Param categoryId query int false "categoryId of Product"
// @Param code query string false "code of Product"
// @Param status query string false "Status of Product (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Product}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/products [get]
func (a *Product) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.ProductQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.ProductBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
type Option struct {
Value interface{} `json:"value"`
Label string `json:"label"`
Children []Option `json:"children"`
}
func ToOptions(categories *[]schema.ProductCategory) []Option {
if categories == nil {
return []Option{}
}
optMap := make(map[uint]*Option, len(*categories))
for _, cat := range *categories {
optMap[cat.ID] = &Option{
Value: cat.ID,
Label: cat.Label,
Children: []Option{},
}
}
var roots []Option
for _, cat := range *categories {
node := optMap[cat.ID]
if cat.ParentID == 0 {
roots = append(roots, *node)
} else if parent, ok := optMap[cat.ParentID]; ok {
parent.Children = append(parent.Children, *node)
}
}
return roots
}
// @Tags 产品模块
// @Security ApiKeyAuth
// @Summary Query ProductCategory list
// @Success 200 {object} util.ResponseResult{data=[]schema.ProductCategory}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/products/categorys [get]
func (a *Product) QueryCategory(c *gin.Context) {
ctx := c.Request.Context()
result, err := a.ProductBIZ.QueryProductCategory(ctx)
if err != nil {
util.ResError(c, err)
return
}
list := ToOptions(result)
util.ResSuccess(c, list)
}
// @Tags 产品模块
// @Security ApiKeyAuth
// @Summary Get Product record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Product}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/products/{id} [get]
func (a *Product) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.ProductBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags 产品模块
// @Security ApiKeyAuth
// @Summary Create Product record
// @Param body body schema.ProductForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Product}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/products [post]
func (a *Product) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.ProductForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.ProductBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 产品模块
// @Security ApiKeyAuth
// @Summary Update Product record by ID
// @Param id path string true "unique id"
// @Param body body schema.ProductForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/products/{id} [put]
func (a *Product) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.ProductForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.ProductBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags 产品模块
// @Security ApiKeyAuth
// @Summary Delete Product record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/products/{id} [delete]
func (a *Product) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.ProductBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,133 @@
package api
import (
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/gin-gonic/gin"
)
// Role management for RBAC
type Role struct {
RoleBIZ *biz.Role
}
// @Tags RoleAPI
// @Security ApiKeyAuth
// @Summary Query role list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param name query string false "Display name of role"
// @Param status query string false "Status of role (disabled, enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Role}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/roles [get]
func (a *Role) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.RoleQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.RoleBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags RoleAPI
// @Security ApiKeyAuth
// @Summary Get role record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Role}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/roles/{id} [get]
func (a *Role) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.RoleBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags RoleAPI
// @Security ApiKeyAuth
// @Summary Create role record
// @Param body body schema.RoleForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Role}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/roles [post]
func (a *Role) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.RoleForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
} else if err := item.Validate(); err != nil {
util.ResError(c, err)
return
}
result, err := a.RoleBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags RoleAPI
// @Security ApiKeyAuth
// @Summary Update role record by ID
// @Param id path string true "unique id"
// @Param body body schema.RoleForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/roles/{id} [put]
func (a *Role) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.RoleForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
} else if err := item.Validate(); err != nil {
util.ResError(c, err)
return
}
err := a.RoleBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags RoleAPI
// @Security ApiKeyAuth
// @Summary Delete role record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/roles/{id} [delete]
func (a *Role) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.RoleBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,126 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
type Team struct {
TeamBIZ *biz.Team
}
// @Tags 团队模块
// @Security ApiKeyAuth
// @Summary Query Team list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param name query string false "name of Team"
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Team}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/teams [get]
func (a *Team) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.TeamQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.TeamBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags 团队模块
// @Security ApiKeyAuth
// @Summary Get Team record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Team}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/teams/{id} [get]
func (a *Team) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.TeamBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags 团队模块
// @Security ApiKeyAuth
// @Summary Create Article record
// @Param body body schema.TeamForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Team}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/teams [post]
func (a *Team) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.TeamForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.TeamBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 团队模块
// @Security ApiKeyAuth
// @Summary Update Article record by ID
// @Param id path string true "unique id"
// @Param body body schema.TeamForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/teams/{id} [put]
func (a *Team) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.TeamForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.TeamBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags 团队模块
// @Security ApiKeyAuth
// @Summary Delete Article record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/teams/{id} [delete]
func (a *Team) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.TeamBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,24 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/pkg/util"
)
type Upload struct {
UploadBIZ *biz.Upload
}
func (a *Upload) SaveFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
util.ResError(c, err)
return
}
result, err := a.UploadBIZ.SaveFile(c, file)
fileURL := fmt.Sprintf("%s/%s", config.C.FileConfig.StaticPrefix, result)
util.ResSuccess(c, fileURL)
}

View File

@ -0,0 +1,152 @@
package api
import (
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/gin-gonic/gin"
)
// User management for RBAC
type User struct {
UserBIZ *biz.User
}
// @Tags UserAPI
// @Security ApiKeyAuth
// @Summary Query user list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param username query string false "Username for login"
// @Param name query string false "Name of user"
// @Param status query string false "Status of user (activated, freezed)"
// @Success 200 {object} util.ResponseResult{data=[]schema.User}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/users [get]
func (a *User) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.UserQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.UserBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags UserAPI
// @Security ApiKeyAuth
// @Summary Get user record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.User}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/users/{id} [get]
func (a *User) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.UserBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags UserAPI
// @Security ApiKeyAuth
// @Summary Create user record
// @Param body body schema.UserForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.User}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/users [post]
func (a *User) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.UserForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
} else if err := item.Validate(); err != nil {
util.ResError(c, err)
return
}
result, err := a.UserBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags UserAPI
// @Security ApiKeyAuth
// @Summary Update user record by ID
// @Param id path string true "unique id"
// @Param body body schema.UserForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/users/{id} [put]
func (a *User) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.UserForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
} else if err := item.Validate(); err != nil {
util.ResError(c, err)
return
}
err := a.UserBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags UserAPI
// @Security ApiKeyAuth
// @Summary Delete user record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/users/{id} [delete]
func (a *User) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.UserBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags UserAPI
// @Security ApiKeyAuth
// @Summary Reset user password by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/users/{id}/reset-pwd [patch]
func (a *User) ResetPassword(c *gin.Context) {
ctx := c.Request.Context()
err := a.UserBIZ.ResetPassword(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,127 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
// Banner management for RBAC
type Video struct {
VideoBIZ *biz.Video
}
// @Tags VideoAPI
// @Security ApiKeyAuth
// @Summary Query Video list
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param title query string false "title of Video"
// @Param status query string false "Status of banner (disabled,enabled)"
// @Success 200 {object} util.ResponseResult{data=[]schema.Video}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/videos [get]
func (a *Video) Query(c *gin.Context) {
ctx := c.Request.Context()
var params schema.VideoQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.VideoBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}
// @Tags VideoAPI
// @Security ApiKeyAuth
// @Summary Get Video record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult{data=schema.Video}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/videos/{id} [get]
func (a *Video) Get(c *gin.Context) {
ctx := c.Request.Context()
item, err := a.VideoBIZ.Get(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, item)
}
// @Tags VideoAPI
// @Security ApiKeyAuth
// @Summary Create banner record
// @Param body body schema.VideoForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.Video}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/videos [post]
func (a *Video) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.VideoForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.VideoBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags VideoAPI
// @Security ApiKeyAuth
// @Summary Update Video record by ID
// @Param id path string true "unique id"
// @Param body body schema.BannerForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/videos/{id} [put]
func (a *Video) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.VideoForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.VideoBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}
// @Tags VideoAPI
// @Security ApiKeyAuth
// @Summary Delete Video record by ID
// @Param id path string true "unique id"
// @Success 200 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/videos/{id} [delete]
func (a *Video) Delete(c *gin.Context) {
ctx := c.Request.Context()
err := a.VideoBIZ.Delete(ctx, c.Param("id"))
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,56 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
// Banner management for RBAC
type Web struct {
ArticleBIZ *biz.Article
JobBIZ *biz.Job
}
// @Tags 网页模块
// @Security ApiKeyAuth
// @Summary 获取职位列表
// @Success 200 {object} util.ResponseResult{data=[]schema.WebJobData}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/web/jobs [get]
func (a *Web) QueryJob(c *gin.Context) {
ctx := c.Request.Context()
list, err := a.JobBIZ.QueryWebJobList(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, list)
}
// @Tags 网页模块
// @Security ApiKeyAuth
// @Summary 获取公司动态
// @Param current query int true "pagination index" default(1)
// @Param pageSize query int true "pagination size" default(10)
// @Param jobAreaId query string false "jobAreaId of Job"
// @Success 200 {object} util.ResponseResult{data=[]schema.Article}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/web/articles [get]
func (a *Web) QueryArticle(c *gin.Context) {
ctx := c.Request.Context()
var params schema.ArticleQueryParam
if err := util.ParseQuery(c, &params); err != nil {
util.ResError(c, err)
return
}
result, err := a.ArticleBIZ.Query(ctx, params)
if err != nil {
util.ResError(c, err)
return
}
util.ResPage(c, result.Data, result.PageResult)
}

View File

@ -0,0 +1,82 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
// Banner management for RBAC
type WebSite struct {
WebSiteBIZ *biz.WebSite
}
// @Tags 网页设置
// @Security ApiKeyAuth
// @Summary Query WebSite info
// @Success 200 {object} util.ResponseResult{data=[]schema.WebSite}
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/web_site [get]
func (a *WebSite) Query(c *gin.Context) {
ctx := c.Request.Context()
result, err := a.WebSiteBIZ.Query(ctx)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags ArticleAPI
// @Security ApiKeyAuth
// @Summary Create WebSite record
// @Param body body schema.WebSiteForm true "Request body"
// @Success 200 {object} util.ResponseResult{data=schema.WebSite}
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/web_site [post]
func (a *WebSite) Create(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.WebSiteForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
result, err := a.WebSiteBIZ.Create(ctx, item)
if err != nil {
util.ResError(c, err)
return
}
util.ResSuccess(c, result)
}
// @Tags 网页设置
// @Security ApiKeyAuth
// @Summary Update WebSite record by ID
// @Param id path string true "unique id"
// @Param body body schema.WebSiteForm true "Request body"
// @Success 200 {object} util.ResponseResult
// @Failure 400 {object} util.ResponseResult
// @Failure 401 {object} util.ResponseResult
// @Failure 500 {object} util.ResponseResult
// @Router /api/v1/web_site/{id} [put]
func (a *WebSite) Update(c *gin.Context) {
ctx := c.Request.Context()
item := new(schema.WebSiteForm)
if err := util.ParseJSON(c, item); err != nil {
util.ResError(c, err)
return
}
err := a.WebSiteBIZ.Update(ctx, c.Param("id"), item)
if err != nil {
util.ResError(c, err)
return
}
util.ResOK(c)
}

View File

@ -0,0 +1,100 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type Article struct {
Cache cachex.Cacher
Trans *util.Trans
ArticleDAL *dal.Article
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Article) Query(ctx context.Context, params schema.ArticleQueryParam) (*schema.ArticleQueryResult, error) {
params.Pagination = true
result, err := a.ArticleDAL.Query(ctx, params, schema.ArticleQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.ASC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Article) Get(ctx context.Context, id string) (*schema.Article, error) {
article, err := a.ArticleDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if article == nil {
return nil, errors.NotFound("", "Banner not found")
}
return article, nil
}
// Create a new role in the data access object.
func (a *Article) Create(ctx context.Context, formItem *schema.ArticleForm) (*schema.Article, error) {
article := &schema.Article{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(article); err != nil {
return nil, err
}
if err := a.ArticleDAL.Create(ctx, article); err != nil {
return nil, err
}
return article, nil
}
// Update the specified role in the data access object.
func (a *Article) Update(ctx context.Context, id string, formItem *schema.ArticleForm) error {
article, err := a.ArticleDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(article); err != nil {
return err
}
article.UpdatedAt = time.Now()
if err := a.ArticleDAL.Update(ctx, article); err != nil {
return err
}
return nil
}
// Delete the specified role from the data access object.
func (a *Article) Delete(ctx context.Context, id string) error {
exists, err := a.ArticleDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.ArticleDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,99 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type Banner struct {
Cache cachex.Cacher
Trans *util.Trans
BannerDAL *dal.Banner
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Banner) Query(ctx context.Context, params schema.BannerQueryParam) (*schema.BannerQueryResult, error) {
params.Pagination = true
result, err := a.BannerDAL.Query(ctx, params, schema.BannerQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Banner) Get(ctx context.Context, id string) (*schema.Banner, error) {
banner, err := a.BannerDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if banner == nil {
return nil, errors.NotFound("", "Banner not found")
}
return banner, nil
}
// Create a new role in the data access object.
func (a *Banner) Create(ctx context.Context, formItem *schema.BannerForm) (*schema.Banner, error) {
banner := &schema.Banner{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(banner); err != nil {
return nil, err
}
if err := a.BannerDAL.Create(ctx, banner); err != nil {
return nil, err
}
return banner, nil
}
// Update the specified role in the data access object.
func (a *Banner) Update(ctx context.Context, id string, formItem *schema.BannerForm) error {
banner, err := a.BannerDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(banner); err != nil {
return err
}
banner.UpdatedAt = time.Now()
if err := a.BannerDAL.Update(ctx, banner); err != nil {
return err
}
return nil
}
// Delete the specified role from the data access object.
func (a *Banner) Delete(ctx context.Context, id string) error {
exists, err := a.BannerDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.BannerDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,178 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type Job struct {
Cache cachex.Cacher
Trans *util.Trans
JobDAL *dal.Job
JobAreaDAL *dal.JobArea
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Job) QueryJobArea(ctx context.Context) (*[]schema.JobArea, error) {
return a.JobAreaDAL.Query(ctx)
}
// Create a new role in the data access object.
func (a *Job) CreateJobArea(ctx context.Context, formItem *schema.JobAreaForm) (*schema.JobArea, error) {
area := &schema.JobArea{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
area.Name = formItem.Name
area.Status = formItem.Status
if err := a.JobAreaDAL.Create(ctx, area); err != nil {
return nil, err
}
return area, nil
}
func (a *Job) QueryWebJobList(ctx context.Context) ([]*schema.WebJobData, error) {
var result []*schema.WebJobData
query, err := a.JobAreaDAL.Query(ctx)
if err != nil {
return nil, err
}
for _, area := range *query {
if area.Status == "enabled" {
queryResult, err := a.JobDAL.Query(ctx, schema.JobQueryParam{JobAreaID: area.ID, Status: "enabled"}, schema.JobQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.ASC},
},
}})
if err == nil {
var info schema.WebJobData
info.JobAreaTitle = area.Name
info.JobList = queryResult.Data
result = append(result, &info)
}
}
}
return result, nil
}
// Update the specified role in the data access object.
func (a *Job) UpdateJobArea(ctx context.Context, id string, formItem *schema.JobAreaForm) error {
area, err := a.JobAreaDAL.Get(ctx, id)
if err != nil {
return err
}
area.UpdatedAt = time.Now()
area.Name = formItem.Name
area.Status = formItem.Status
if err := a.JobAreaDAL.Update(ctx, area); err != nil {
return err
}
return nil
}
// Delete the specified role from the data access object.
func (a *Job) DeleteJobArea(ctx context.Context, id string) error {
exists, err := a.JobAreaDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.JobAreaDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Job) Query(ctx context.Context, params schema.JobQueryParam) (*schema.JobQueryResult, error) {
params.Pagination = true
result, err := a.JobDAL.Query(ctx, params, schema.JobQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.ASC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Job) Get(ctx context.Context, id string) (*schema.Job, error) {
job, err := a.JobDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if job == nil {
return nil, errors.NotFound("", "Banner not found")
}
return job, nil
}
// Create a new role in the data access object.
func (a *Job) Create(ctx context.Context, formItem *schema.JobForm) (*schema.Job, error) {
job := &schema.Job{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(job); err != nil {
return nil, err
}
if err := a.JobDAL.Create(ctx, job); err != nil {
return nil, err
}
return job, nil
}
// Update the specified role in the data access object.
func (a *Job) Update(ctx context.Context, id string, formItem *schema.JobForm) error {
job, err := a.JobDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(job); err != nil {
return err
}
job.UpdatedAt = time.Now()
if err := a.JobDAL.Update(ctx, job); err != nil {
return err
}
return nil
}
// Delete the specified role from the data access object.
func (a *Job) Delete(ctx context.Context, id string) error {
exists, err := a.JobDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.JobDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,31 @@
package biz
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/util"
)
// Logger management
type Logger struct {
LoggerDAL *dal.Logger
}
// Query loggers from the data access object based on the provided parameters and options.
func (a *Logger) Query(ctx context.Context, params schema.LoggerQueryParam) (*schema.LoggerQueryResult, error) {
params.Pagination = true
result, err := a.LoggerDAL.Query(ctx, params, schema.LoggerQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}

View File

@ -0,0 +1,374 @@
package biz
import (
"context"
"net/http"
"sort"
"time"
"github.com/LyricTian/captcha"
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/crypto/hash"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/jwtx"
"github.com/guxuan/hailin_service/pkg/logging"
"github.com/guxuan/hailin_service/pkg/util"
"go.uber.org/zap"
)
// Login management for RBAC
type Login struct {
Cache cachex.Cacher
Auth jwtx.Auther
UserDAL *dal.User
UserRoleDAL *dal.UserRole
MenuDAL *dal.Menu
UserBIZ *User
}
func (a *Login) ParseUserID(c *gin.Context) (string, error) {
rootID := config.C.General.Root.ID
if config.C.Middleware.Auth.Disable {
return rootID, nil
}
invalidToken := errors.Unauthorized(config.ErrInvalidTokenID, "Invalid access token")
token := util.GetToken(c)
if token == "" {
return "", invalidToken
}
ctx := c.Request.Context()
ctx = util.NewUserToken(ctx, token)
userID, err := a.Auth.ParseSubject(ctx, token)
if err != nil {
if err == jwtx.ErrInvalidToken {
return "", invalidToken
}
return "", err
} else if userID == rootID {
c.Request = c.Request.WithContext(util.NewIsRootUser(ctx))
return userID, nil
}
userCacheVal, ok, err := a.Cache.Get(ctx, config.CacheNSForUser, userID)
if err != nil {
return "", err
} else if ok {
userCache := util.ParseUserCache(userCacheVal)
c.Request = c.Request.WithContext(util.NewUserCache(ctx, userCache))
return userID, nil
}
// Check user status, if not activated, force to logout
user, err := a.UserDAL.Get(ctx, userID, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{SelectFields: []string{"status"}},
})
if err != nil {
return "", err
} else if user == nil || user.Status != schema.UserStatusActivated {
return "", invalidToken
}
roleIDs, err := a.UserBIZ.GetRoleIDs(ctx, userID)
if err != nil {
return "", err
}
userCache := util.UserCache{
RoleIDs: roleIDs,
}
err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String())
if err != nil {
return "", err
}
c.Request = c.Request.WithContext(util.NewUserCache(ctx, userCache))
return userID, nil
}
// This function generates a new captcha ID and returns it as a `schema.Captcha` struct. The length of
// the captcha is determined by the `config.C.Util.Captcha.Length` configuration value.
func (a *Login) GetCaptcha(ctx context.Context) (*schema.Captcha, error) {
return &schema.Captcha{
CaptchaID: captcha.NewLen(config.C.Util.Captcha.Length),
}, nil
}
// Response captcha image
func (a *Login) ResponseCaptcha(ctx context.Context, w http.ResponseWriter, id string, reload bool) error {
if reload && !captcha.Reload(id) {
return errors.NotFound("", "Captcha id not found")
}
err := captcha.WriteImage(w, id, config.C.Util.Captcha.Width, config.C.Util.Captcha.Height)
if err != nil {
if err == captcha.ErrNotFound {
return errors.NotFound("", "Captcha id not found")
}
return err
}
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("Content-Type", "image/png")
return nil
}
func (a *Login) genUserToken(ctx context.Context, userID string) (*schema.LoginToken, error) {
token, err := a.Auth.GenerateToken(ctx, userID)
if err != nil {
return nil, err
}
tokenBuf, err := token.EncodeToJSON()
if err != nil {
return nil, err
}
logging.Context(ctx).Info("Generate user token", zap.Any("token", string(tokenBuf)))
return &schema.LoginToken{
AccessToken: token.GetAccessToken(),
TokenType: token.GetTokenType(),
ExpiresAt: token.GetExpiresAt(),
}, nil
}
func (a *Login) Login(ctx context.Context, formItem *schema.LoginForm) (*schema.LoginToken, error) {
// verify captcha
//if !captcha.VerifyString(formItem.CaptchaID, formItem.CaptchaCode) {
// return nil, errors.BadRequest(config.ErrInvalidCaptchaID, "Incorrect captcha")
//}
ctx = logging.NewTag(ctx, logging.TagKeyLogin)
// login by root
if formItem.Username == config.C.General.Root.Username {
if formItem.Password != config.C.General.Root.Password {
return nil, errors.BadRequest(config.ErrInvalidUsernameOrPassword, "账号密码错误!")
}
userID := config.C.General.Root.ID
ctx = logging.NewUserID(ctx, userID)
logging.Context(ctx).Info("Login by root")
return a.genUserToken(ctx, userID)
}
// get user info
user, err := a.UserDAL.GetByUsername(ctx, formItem.Username, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"id", "password", "status"},
},
})
if err != nil {
return nil, err
} else if user == nil {
return nil, errors.BadRequest(config.ErrInvalidUsernameOrPassword, "Incorrect username or password")
} else if user.Status != schema.UserStatusActivated {
return nil, errors.BadRequest("", "User status is not activated, please contact the administrator")
}
// check password
if err := hash.CompareHashAndPassword(user.Password, formItem.Password); err != nil {
return nil, errors.BadRequest(config.ErrInvalidUsernameOrPassword, "Incorrect username or password")
}
userID := user.ID
ctx = logging.NewUserID(ctx, userID)
// set user cache with role ids
roleIDs, err := a.UserBIZ.GetRoleIDs(ctx, userID)
if err != nil {
return nil, err
}
userCache := util.UserCache{RoleIDs: roleIDs}
err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String(),
time.Duration(config.C.Dictionary.UserCacheExp)*time.Hour)
if err != nil {
logging.Context(ctx).Error("Failed to set cache", zap.Error(err))
}
logging.Context(ctx).Info("Login success", zap.String("username", formItem.Username))
// generate token
return a.genUserToken(ctx, userID)
}
func (a *Login) RefreshToken(ctx context.Context) (*schema.LoginToken, error) {
userID := util.FromUserID(ctx)
user, err := a.UserDAL.Get(ctx, userID, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"status"},
},
})
if err != nil {
return nil, err
} else if user == nil {
return nil, errors.BadRequest("", "Incorrect user")
} else if user.Status != schema.UserStatusActivated {
return nil, errors.BadRequest("", "User status is not activated, please contact the administrator")
}
return a.genUserToken(ctx, userID)
}
func (a *Login) Logout(ctx context.Context) error {
userToken := util.FromUserToken(ctx)
if userToken == "" {
return nil
}
ctx = logging.NewTag(ctx, logging.TagKeyLogout)
if err := a.Auth.DestroyToken(ctx, userToken); err != nil {
return err
}
userID := util.FromUserID(ctx)
err := a.Cache.Delete(ctx, config.CacheNSForUser, userID)
if err != nil {
logging.Context(ctx).Error("Failed to delete user cache", zap.Error(err))
}
logging.Context(ctx).Info("Logout success")
return nil
}
// Get user info
func (a *Login) GetUserInfo(ctx context.Context) (*schema.User, error) {
if util.FromIsRootUser(ctx) {
return &schema.User{
ID: config.C.General.Root.ID,
Username: config.C.General.Root.Username,
Name: config.C.General.Root.Name,
Status: schema.UserStatusActivated,
}, nil
}
userID := util.FromUserID(ctx)
user, err := a.UserDAL.Get(ctx, userID, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{
OmitFields: []string{"password"},
},
})
if err != nil {
return nil, err
} else if user == nil {
return nil, errors.NotFound("", "User not found")
}
userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{
UserID: userID,
}, schema.UserRoleQueryOptions{
JoinRole: true,
})
if err != nil {
return nil, err
}
user.Roles = userRoleResult.Data
return user, nil
}
// Change login password
func (a *Login) UpdatePassword(ctx context.Context, updateItem *schema.UpdateLoginPassword) error {
if util.FromIsRootUser(ctx) {
return errors.BadRequest("", "Root user cannot change password")
}
userID := util.FromUserID(ctx)
user, err := a.UserDAL.Get(ctx, userID, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"password"},
},
})
if err != nil {
return err
} else if user == nil {
return errors.NotFound("", "User not found")
}
// check old password
if err := hash.CompareHashAndPassword(user.Password, updateItem.OldPassword); err != nil {
return errors.BadRequest("", "Incorrect old password")
}
// update password
newPassword, err := hash.GeneratePassword(updateItem.NewPassword)
if err != nil {
return err
}
return a.UserDAL.UpdatePasswordByID(ctx, userID, newPassword)
}
// Query menus based on user permissions
func (a *Login) QueryMenus(ctx context.Context) (schema.Menus, error) {
menuQueryParams := schema.MenuQueryParam{
Status: schema.MenuStatusEnabled,
}
isRoot := util.FromIsRootUser(ctx)
if !isRoot {
menuQueryParams.UserID = util.FromUserID(ctx)
}
menuResult, err := a.MenuDAL.Query(ctx, menuQueryParams, schema.MenuQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: schema.MenusOrderParams,
},
})
if err != nil {
return nil, err
} else if isRoot {
return menuResult.Data.ToTree(), nil
}
// fill parent menus
if parentIDs := menuResult.Data.SplitParentIDs(); len(parentIDs) > 0 {
var missMenusIDs []string
menuIDMapper := menuResult.Data.ToMap()
for _, parentID := range parentIDs {
if _, ok := menuIDMapper[parentID]; !ok {
missMenusIDs = append(missMenusIDs, parentID)
}
}
if len(missMenusIDs) > 0 {
parentResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{
InIDs: missMenusIDs,
})
if err != nil {
return nil, err
}
menuResult.Data = append(menuResult.Data, parentResult.Data...)
sort.Sort(menuResult.Data)
}
}
return menuResult.Data.ToTree(), nil
}
// Update current user info
func (a *Login) UpdateUser(ctx context.Context, updateItem *schema.UpdateCurrentUser) error {
if util.FromIsRootUser(ctx) {
return errors.BadRequest("", "Root user cannot update")
}
userID := util.FromUserID(ctx)
user, err := a.UserDAL.Get(ctx, userID)
if err != nil {
return err
} else if user == nil {
return errors.NotFound("", "User not found")
}
user.Name = updateItem.Name
user.Phone = updateItem.Phone
user.Email = updateItem.Email
user.Remark = updateItem.Remark
return a.UserDAL.Update(ctx, user, "name", "phone", "email", "remark")
}

View File

@ -0,0 +1,99 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type Memorabilia struct {
Cache cachex.Cacher
Trans *util.Trans
MemorabiliaDAL *dal.Memorabilia
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Memorabilia) Query(ctx context.Context, params schema.MemorabiliaQueryParam) (*schema.MemorabiliaQueryResult, error) {
params.Pagination = true
result, err := a.MemorabiliaDAL.Query(ctx, params, schema.MemorabiliaQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Memorabilia) Get(ctx context.Context, id string) (*schema.Memorabilia, error) {
memorabilia, err := a.MemorabiliaDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if memorabilia == nil {
return nil, errors.NotFound("", "Banner not found")
}
return memorabilia, nil
}
// Create a new role in the data access object.
func (a *Memorabilia) Create(ctx context.Context, formItem *schema.MemorabiliaForm) (*schema.Memorabilia, error) {
memorabilia := &schema.Memorabilia{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(memorabilia); err != nil {
return nil, err
}
if err := a.MemorabiliaDAL.Create(ctx, memorabilia); err != nil {
return nil, err
}
return memorabilia, nil
}
// Update the specified role in the data access object.
func (a *Memorabilia) Update(ctx context.Context, id string, formItem *schema.MemorabiliaForm) error {
memorabilia, err := a.MemorabiliaDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(memorabilia); err != nil {
return err
}
memorabilia.UpdatedAt = time.Now()
if err := a.MemorabiliaDAL.Update(ctx, memorabilia); err != nil {
return err
}
return nil
}
// Delete the specified role from the data access object.
func (a *Memorabilia) Delete(ctx context.Context, id string) error {
exists, err := a.MemorabiliaDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.MemorabiliaDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,515 @@
package biz
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/encoding/json"
"github.com/guxuan/hailin_service/pkg/encoding/yaml"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/logging"
"github.com/guxuan/hailin_service/pkg/util"
"go.uber.org/zap"
)
// Menu management for RBAC
type Menu struct {
Cache cachex.Cacher
Trans *util.Trans
MenuDAL *dal.Menu
MenuResourceDAL *dal.MenuResource
RoleMenuDAL *dal.RoleMenu
}
func (a *Menu) InitFromFile(ctx context.Context, menuFile string) error {
f, err := os.ReadFile(menuFile)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
logging.Context(ctx).Warn("Menu data file not found, skip init menu data from file", zap.String("file", menuFile))
return nil
}
return err
}
var menus schema.Menus
if ext := filepath.Ext(menuFile); ext == ".json" {
if err := json.Unmarshal(f, &menus); err != nil {
return errors.Wrapf(err, "Unmarshal JSON file '%s' failed", menuFile)
}
} else if ext == ".yaml" || ext == ".yml" {
if err := yaml.Unmarshal(f, &menus); err != nil {
return errors.Wrapf(err, "Unmarshal YAML file '%s' failed", menuFile)
}
} else {
return errors.Errorf("Unsupported file type '%s'", ext)
}
return a.Trans.Exec(ctx, func(ctx context.Context) error {
return a.createInBatchByParent(ctx, menus, nil)
})
}
func (a *Menu) createInBatchByParent(ctx context.Context, items schema.Menus, parent *schema.Menu) error {
total := len(items)
for i, item := range items {
var parentID string
if parent != nil {
parentID = parent.ID
}
var (
menuItem *schema.Menu
err error
)
if item.ID != "" {
menuItem, err = a.MenuDAL.Get(ctx, item.ID)
} else if item.Code != "" {
menuItem, err = a.MenuDAL.GetByCodeAndParentID(ctx, item.Code, parentID)
} else if item.Name != "" {
menuItem, err = a.MenuDAL.GetByNameAndParentID(ctx, item.Name, parentID)
}
if err != nil {
return err
}
if item.Status == "" {
item.Status = schema.MenuStatusEnabled
}
if menuItem != nil {
changed := false
if menuItem.Name != item.Name {
menuItem.Name = item.Name
changed = true
}
if menuItem.Description != item.Description {
menuItem.Description = item.Description
changed = true
}
if menuItem.Path != item.Path {
menuItem.Path = item.Path
changed = true
}
if menuItem.Type != item.Type {
menuItem.Type = item.Type
changed = true
}
if menuItem.Sequence != item.Sequence {
menuItem.Sequence = item.Sequence
changed = true
}
if menuItem.Status != item.Status {
menuItem.Status = item.Status
changed = true
}
if changed {
menuItem.UpdatedAt = time.Now()
if err := a.MenuDAL.Update(ctx, menuItem); err != nil {
return err
}
}
} else {
if item.ID == "" {
item.ID = util.NewXID()
}
if item.Sequence == 0 {
item.Sequence = total - i
}
item.ParentID = parentID
if parent != nil {
item.ParentPath = parent.ParentPath + parentID + util.TreePathDelimiter
}
menuItem = item
if err := a.MenuDAL.Create(ctx, item); err != nil {
return err
}
}
for _, res := range item.Resources {
if res.ID != "" {
exists, err := a.MenuResourceDAL.Exists(ctx, res.ID)
if err != nil {
return err
} else if exists {
continue
}
}
if res.Path != "" {
exists, err := a.MenuResourceDAL.ExistsMethodPathByMenuID(ctx, res.Method, res.Path, menuItem.ID)
if err != nil {
return err
} else if exists {
continue
}
}
if res.ID == "" {
res.ID = util.NewXID()
}
res.MenuID = menuItem.ID
if err := a.MenuResourceDAL.Create(ctx, res); err != nil {
return err
}
}
if item.Children != nil {
if err := a.createInBatchByParent(ctx, *item.Children, menuItem); err != nil {
return err
}
}
}
return nil
}
// Query menus from the data access object based on the provided parameters and options.
func (a *Menu) Query(ctx context.Context, params schema.MenuQueryParam) (*schema.MenuQueryResult, error) {
params.Pagination = false
if err := a.fillQueryParam(ctx, &params); err != nil {
return nil, err
}
result, err := a.MenuDAL.Query(ctx, params, schema.MenuQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: schema.MenusOrderParams,
},
})
if err != nil {
return nil, err
}
if params.LikeName != "" || params.CodePath != "" {
result.Data, err = a.appendChildren(ctx, result.Data)
if err != nil {
return nil, err
}
}
if params.IncludeResources {
for i, item := range result.Data {
resResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{
MenuID: item.ID,
})
if err != nil {
return nil, err
}
result.Data[i].Resources = resResult.Data
}
}
result.Data = result.Data.ToTree()
return result, nil
}
func (a *Menu) fillQueryParam(ctx context.Context, params *schema.MenuQueryParam) error {
if params.CodePath != "" {
var (
codes []string
lastMenu schema.Menu
)
for _, code := range strings.Split(params.CodePath, util.TreePathDelimiter) {
if code == "" {
continue
}
codes = append(codes, code)
menu, err := a.MenuDAL.GetByCodeAndParentID(ctx, code, lastMenu.ParentID, schema.MenuQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"id", "parent_id", "parent_path"},
},
})
if err != nil {
return err
} else if menu == nil {
return errors.NotFound("", "Menu not found by code '%s'", strings.Join(codes, util.TreePathDelimiter))
}
lastMenu = *menu
}
params.ParentPathPrefix = lastMenu.ParentPath + lastMenu.ID + util.TreePathDelimiter
}
return nil
}
func (a *Menu) appendChildren(ctx context.Context, data schema.Menus) (schema.Menus, error) {
if len(data) == 0 {
return data, nil
}
existsInData := func(id string) bool {
for _, item := range data {
if item.ID == id {
return true
}
}
return false
}
for _, item := range data {
childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{
ParentPathPrefix: item.ParentPath + item.ID + util.TreePathDelimiter,
})
if err != nil {
return nil, err
}
for _, child := range childResult.Data {
if existsInData(child.ID) {
continue
}
data = append(data, child)
}
}
if parentIDs := data.SplitParentIDs(); len(parentIDs) > 0 {
parentResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{
InIDs: parentIDs,
})
if err != nil {
return nil, err
}
for _, p := range parentResult.Data {
if existsInData(p.ID) {
continue
}
data = append(data, p)
}
}
sort.Sort(data)
return data, nil
}
// Get the specified menu from the data access object.
func (a *Menu) Get(ctx context.Context, id string) (*schema.Menu, error) {
menu, err := a.MenuDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if menu == nil {
return nil, errors.NotFound("", "Menu not found")
}
menuResResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{
MenuID: menu.ID,
})
if err != nil {
return nil, err
}
menu.Resources = menuResResult.Data
return menu, nil
}
// Create a new menu in the data access object.
func (a *Menu) Create(ctx context.Context, formItem *schema.MenuForm) (*schema.Menu, error) {
if config.C.General.DenyOperateMenu {
return nil, errors.BadRequest("", "Menu creation is not allowed")
}
menu := &schema.Menu{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if parentID := formItem.ParentID; parentID != "" {
parent, err := a.MenuDAL.Get(ctx, parentID)
if err != nil {
return nil, err
} else if parent == nil {
return nil, errors.NotFound("", "Parent not found")
}
menu.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter
}
if exists, err := a.MenuDAL.ExistsCodeByParentID(ctx, formItem.Code, formItem.ParentID); err != nil {
return nil, err
} else if exists {
return nil, errors.BadRequest("", "Menu code already exists at the same level")
}
if err := formItem.FillTo(menu); err != nil {
return nil, err
}
err := a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.MenuDAL.Create(ctx, menu); err != nil {
return err
}
for _, res := range formItem.Resources {
res.ID = util.NewXID()
res.MenuID = menu.ID
res.CreatedAt = time.Now()
if err := a.MenuResourceDAL.Create(ctx, res); err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
return menu, nil
}
// Update the specified menu in the data access object.
func (a *Menu) Update(ctx context.Context, id string, formItem *schema.MenuForm) error {
if config.C.General.DenyOperateMenu {
return errors.BadRequest("", "Menu update is not allowed")
}
menu, err := a.MenuDAL.Get(ctx, id)
if err != nil {
return err
} else if menu == nil {
return errors.NotFound("", "Menu not found")
}
oldParentPath := menu.ParentPath
oldStatus := menu.Status
var childData schema.Menus
if menu.ParentID != formItem.ParentID {
if parentID := formItem.ParentID; parentID != "" {
parent, err := a.MenuDAL.Get(ctx, parentID)
if err != nil {
return err
} else if parent == nil {
return errors.NotFound("", "Parent not found")
}
menu.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter
} else {
menu.ParentPath = ""
}
childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{
ParentPathPrefix: oldParentPath + menu.ID + util.TreePathDelimiter,
}, schema.MenuQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"id", "parent_path"},
},
})
if err != nil {
return err
}
childData = childResult.Data
}
if menu.Code != formItem.Code {
if exists, err := a.MenuDAL.ExistsCodeByParentID(ctx, formItem.Code, formItem.ParentID); err != nil {
return err
} else if exists {
return errors.BadRequest("", "Menu code already exists at the same level")
}
}
if err := formItem.FillTo(menu); err != nil {
return err
}
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if oldStatus != formItem.Status {
oldPath := oldParentPath + menu.ID + util.TreePathDelimiter
if err := a.MenuDAL.UpdateStatusByParentPath(ctx, oldPath, formItem.Status); err != nil {
return err
}
}
for _, child := range childData {
oldPath := oldParentPath + menu.ID + util.TreePathDelimiter
newPath := menu.ParentPath + menu.ID + util.TreePathDelimiter
err := a.MenuDAL.UpdateParentPath(ctx, child.ID, strings.Replace(child.ParentPath, oldPath, newPath, 1))
if err != nil {
return err
}
}
if err := a.MenuDAL.Update(ctx, menu); err != nil {
return err
}
if err := a.MenuResourceDAL.DeleteByMenuID(ctx, id); err != nil {
return err
}
for _, res := range formItem.Resources {
if res.ID == "" {
res.ID = util.NewXID()
}
res.MenuID = id
if res.CreatedAt.IsZero() {
res.CreatedAt = time.Now()
}
res.UpdatedAt = time.Now()
if err := a.MenuResourceDAL.Create(ctx, res); err != nil {
return err
}
}
return a.syncToCasbin(ctx)
})
}
// Delete the specified menu from the data access object.
func (a *Menu) Delete(ctx context.Context, id string) error {
if config.C.General.DenyOperateMenu {
return errors.BadRequest("", "Menu deletion is not allowed")
}
menu, err := a.MenuDAL.Get(ctx, id)
if err != nil {
return err
} else if menu == nil {
return errors.NotFound("", "Menu not found")
}
childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{
ParentPathPrefix: menu.ParentPath + menu.ID + util.TreePathDelimiter,
}, schema.MenuQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"id"},
},
})
if err != nil {
return err
}
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.delete(ctx, id); err != nil {
return err
}
for _, child := range childResult.Data {
if err := a.delete(ctx, child.ID); err != nil {
return err
}
}
return a.syncToCasbin(ctx)
})
}
func (a *Menu) delete(ctx context.Context, id string) error {
if err := a.MenuDAL.Delete(ctx, id); err != nil {
return err
}
if err := a.MenuResourceDAL.DeleteByMenuID(ctx, id); err != nil {
return err
}
if err := a.RoleMenuDAL.DeleteByMenuID(ctx, id); err != nil {
return err
}
return nil
}
func (a *Menu) syncToCasbin(ctx context.Context) error {
return a.Cache.Set(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin, fmt.Sprintf("%d", time.Now().Unix()))
}

View File

@ -0,0 +1,102 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
type Product struct {
Cache cachex.Cacher
Trans *util.Trans
ProductDAL *dal.Product
ProductCategoryDAL *dal.ProductCategory
}
func (a *Product) QueryProductCategory(ctx context.Context) (*[]schema.ProductCategory, error) {
query, err := a.ProductCategoryDAL.Query(ctx)
if err != nil {
return nil, err
}
return query, nil
}
func (a *Product) Query(ctx context.Context, params schema.ProductQueryParam) (*schema.ProductQueryResult, error) {
params.Pagination = true
result, err := a.ProductDAL.Query(ctx, params, schema.ProductQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
func (a *Product) Get(ctx context.Context, id string) (*schema.Product, error) {
product, err := a.ProductDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if product == nil {
return nil, errors.NotFound("", "Banner not found")
}
return product, nil
}
func (a *Product) Create(ctx context.Context, formItem *schema.ProductForm) (*schema.Product, error) {
product := &schema.Product{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(product); err != nil {
return nil, err
}
if err := a.ProductDAL.Create(ctx, product); err != nil {
return nil, err
}
return product, nil
}
func (a *Product) Update(ctx context.Context, id string, formItem *schema.ProductForm) error {
product, err := a.ProductDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(product); err != nil {
return err
}
product.UpdatedAt = time.Now()
if err := a.ProductDAL.Update(ctx, product); err != nil {
return err
}
return nil
}
func (a *Product) Delete(ctx context.Context, id string) error {
exists, err := a.ProductDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.ProductDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,179 @@
package biz
import (
"context"
"fmt"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type Role struct {
Cache cachex.Cacher
Trans *util.Trans
RoleDAL *dal.Role
RoleMenuDAL *dal.RoleMenu
UserRoleDAL *dal.UserRole
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Role) Query(ctx context.Context, params schema.RoleQueryParam) (*schema.RoleQueryResult, error) {
params.Pagination = true
var selectFields []string
if params.ResultType == schema.RoleResultTypeSelect {
params.Pagination = false
selectFields = []string{"id", "name"}
}
result, err := a.RoleDAL.Query(ctx, params, schema.RoleQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
{Field: "created_at", Direction: util.DESC},
},
SelectFields: selectFields,
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Role) Get(ctx context.Context, id string) (*schema.Role, error) {
role, err := a.RoleDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if role == nil {
return nil, errors.NotFound("", "Role not found")
}
roleMenuResult, err := a.RoleMenuDAL.Query(ctx, schema.RoleMenuQueryParam{
RoleID: id,
})
if err != nil {
return nil, err
}
role.Menus = roleMenuResult.Data
return role, nil
}
// Create a new role in the data access object.
func (a *Role) Create(ctx context.Context, formItem *schema.RoleForm) (*schema.Role, error) {
if exists, err := a.RoleDAL.ExistsCode(ctx, formItem.Code); err != nil {
return nil, err
} else if exists {
return nil, errors.BadRequest("", "Role code already exists")
}
role := &schema.Role{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(role); err != nil {
return nil, err
}
err := a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.RoleDAL.Create(ctx, role); err != nil {
return err
}
for _, roleMenu := range formItem.Menus {
roleMenu.ID = util.NewXID()
roleMenu.RoleID = role.ID
roleMenu.CreatedAt = time.Now()
if err := a.RoleMenuDAL.Create(ctx, roleMenu); err != nil {
return err
}
}
return a.syncToCasbin(ctx)
})
if err != nil {
return nil, err
}
role.Menus = formItem.Menus
return role, nil
}
// Update the specified role in the data access object.
func (a *Role) Update(ctx context.Context, id string, formItem *schema.RoleForm) error {
role, err := a.RoleDAL.Get(ctx, id)
if err != nil {
return err
} else if role == nil {
return errors.NotFound("", "Role not found")
} else if role.Code != formItem.Code {
if exists, err := a.RoleDAL.ExistsCode(ctx, formItem.Code); err != nil {
return err
} else if exists {
return errors.BadRequest("", "Role code already exists")
}
}
if err := formItem.FillTo(role); err != nil {
return err
}
role.UpdatedAt = time.Now()
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.RoleDAL.Update(ctx, role); err != nil {
return err
}
if err := a.RoleMenuDAL.DeleteByRoleID(ctx, id); err != nil {
return err
}
for _, roleMenu := range formItem.Menus {
if roleMenu.ID == "" {
roleMenu.ID = util.NewXID()
}
roleMenu.RoleID = role.ID
if roleMenu.CreatedAt.IsZero() {
roleMenu.CreatedAt = time.Now()
}
roleMenu.UpdatedAt = time.Now()
if err := a.RoleMenuDAL.Create(ctx, roleMenu); err != nil {
return err
}
}
return a.syncToCasbin(ctx)
})
}
// Delete the specified role from the data access object.
func (a *Role) Delete(ctx context.Context, id string) error {
exists, err := a.RoleDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.RoleDAL.Delete(ctx, id); err != nil {
return err
}
if err := a.RoleMenuDAL.DeleteByRoleID(ctx, id); err != nil {
return err
}
if err := a.UserRoleDAL.DeleteByRoleID(ctx, id); err != nil {
return err
}
return a.syncToCasbin(ctx)
})
}
func (a *Role) syncToCasbin(ctx context.Context) error {
return a.Cache.Set(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin, fmt.Sprintf("%d", time.Now().Unix()))
}

View File

@ -0,0 +1,94 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
type Team struct {
Cache cachex.Cacher
Trans *util.Trans
TeamDAL *dal.Team
}
func (a *Team) Query(ctx context.Context, params schema.TeamQueryParam) (*schema.TeamQueryResult, error) {
params.Pagination = true
result, err := a.TeamDAL.Query(ctx, params, schema.TeamQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Team) Get(ctx context.Context, id string) (*schema.Team, error) {
item, err := a.TeamDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if item == nil {
return nil, errors.NotFound("", "Banner not found")
}
return item, nil
}
func (a *Team) Create(ctx context.Context, formItem *schema.TeamForm) (*schema.Team, error) {
team := &schema.Team{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(team); err != nil {
return nil, err
}
if err := a.TeamDAL.Create(ctx, team); err != nil {
return nil, err
}
return team, nil
}
func (a *Team) Update(ctx context.Context, id string, formItem *schema.TeamForm) error {
team, err := a.TeamDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(team); err != nil {
return err
}
team.UpdatedAt = time.Now()
if err := a.TeamDAL.Update(ctx, team); err != nil {
return err
}
return nil
}
func (a *Team) Delete(ctx context.Context, id string) error {
exists, err := a.TeamDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.TeamDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,54 @@
package biz
import (
"context"
"fmt"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/util"
"io"
"mime/multipart"
"os"
"path/filepath"
"strings"
)
// Role management for RBAC
type Upload struct {
Cache cachex.Cacher
Trans *util.Trans
}
func (a *Upload) SaveFile(ctx context.Context, file *multipart.FileHeader) (string, error) {
ext := strings.ToLower(filepath.Ext(file.Filename))
// 简单过滤危险扩展名
blacklist := []string{".exe", ".bat", ".sh", ".php", ".js"}
for _, b := range blacklist {
if ext == b {
return "", fmt.Errorf("不支持的文件类型: %s", ext)
}
}
// 确保上传目录存在
if err := os.MkdirAll(config.C.FileConfig.UploadDir, os.ModePerm); err != nil {
return "", err
}
// 构造保存路径
dstPath := filepath.Join(config.C.FileConfig.UploadDir, file.Filename)
// 打开源文件
srcFile, err := file.Open()
if err != nil {
return "", err
}
defer srcFile.Close()
// 在目标位置创建文件
outFile, err := os.Create(dstPath)
if err != nil {
return "", err
}
defer outFile.Close()
// 复制内容
if _, err := io.Copy(outFile, srcFile); err != nil {
return "", err
}
return file.Filename, nil
}

View File

@ -0,0 +1,227 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/crypto/hash"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// User management for RBAC
type User struct {
Cache cachex.Cacher
Trans *util.Trans
UserDAL *dal.User
UserRoleDAL *dal.UserRole
}
// Query users from the data access object based on the provided parameters and options.
func (a *User) Query(ctx context.Context, params schema.UserQueryParam) (*schema.UserQueryResult, error) {
params.Pagination = true
result, err := a.UserDAL.Query(ctx, params, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
},
OmitFields: []string{"password"},
},
})
if err != nil {
return nil, err
}
if userIDs := result.Data.ToIDs(); len(userIDs) > 0 {
userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{
InUserIDs: userIDs,
}, schema.UserRoleQueryOptions{
JoinRole: true,
})
if err != nil {
return nil, err
}
userRolesMap := userRoleResult.Data.ToUserIDMap()
for _, user := range result.Data {
user.Roles = userRolesMap[user.ID]
}
}
return result, nil
}
// Get the specified user from the data access object.
func (a *User) Get(ctx context.Context, id string) (*schema.User, error) {
user, err := a.UserDAL.Get(ctx, id, schema.UserQueryOptions{
QueryOptions: util.QueryOptions{
OmitFields: []string{"password"},
},
})
if err != nil {
return nil, err
} else if user == nil {
return nil, errors.NotFound("", "User not found")
}
userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{
UserID: id,
})
if err != nil {
return nil, err
}
user.Roles = userRoleResult.Data
return user, nil
}
// Create a new user in the data access object.
func (a *User) Create(ctx context.Context, formItem *schema.UserForm) (*schema.User, error) {
existsUsername, err := a.UserDAL.ExistsUsername(ctx, formItem.Username)
if err != nil {
return nil, err
} else if existsUsername {
return nil, errors.BadRequest("", "Username already exists")
}
user := &schema.User{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if formItem.Password == "" {
formItem.Password = config.C.General.DefaultLoginPwd
}
if err := formItem.FillTo(user); err != nil {
return nil, err
}
err = a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.UserDAL.Create(ctx, user); err != nil {
return err
}
for _, userRole := range formItem.Roles {
userRole.ID = util.NewXID()
userRole.UserID = user.ID
userRole.CreatedAt = time.Now()
if err := a.UserRoleDAL.Create(ctx, userRole); err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
user.Roles = formItem.Roles
return user, nil
}
// Update the specified user in the data access object.
func (a *User) Update(ctx context.Context, id string, formItem *schema.UserForm) error {
user, err := a.UserDAL.Get(ctx, id)
if err != nil {
return err
} else if user == nil {
return errors.NotFound("", "User not found")
} else if user.Username != formItem.Username {
existsUsername, err := a.UserDAL.ExistsUsername(ctx, formItem.Username)
if err != nil {
return err
} else if existsUsername {
return errors.BadRequest("", "Username already exists")
}
}
if err := formItem.FillTo(user); err != nil {
return err
}
user.UpdatedAt = time.Now()
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.UserDAL.Update(ctx, user); err != nil {
return err
}
if err := a.UserRoleDAL.DeleteByUserID(ctx, id); err != nil {
return err
}
for _, userRole := range formItem.Roles {
if userRole.ID == "" {
userRole.ID = util.NewXID()
}
userRole.UserID = user.ID
if userRole.CreatedAt.IsZero() {
userRole.CreatedAt = time.Now()
}
userRole.UpdatedAt = time.Now()
if err := a.UserRoleDAL.Create(ctx, userRole); err != nil {
return err
}
}
return a.Cache.Delete(ctx, config.CacheNSForUser, id)
})
}
// Delete the specified user from the data access object.
func (a *User) Delete(ctx context.Context, id string) error {
exists, err := a.UserDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "User not found")
}
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.UserDAL.Delete(ctx, id); err != nil {
return err
}
if err := a.UserRoleDAL.DeleteByUserID(ctx, id); err != nil {
return err
}
return a.Cache.Delete(ctx, config.CacheNSForUser, id)
})
}
func (a *User) ResetPassword(ctx context.Context, id string) error {
exists, err := a.UserDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "User not found")
}
hashPass, err := hash.GeneratePassword(config.C.General.DefaultLoginPwd)
if err != nil {
return errors.BadRequest("", "Failed to generate hash password: %s", err.Error())
}
return a.Trans.Exec(ctx, func(ctx context.Context) error {
if err := a.UserDAL.UpdatePasswordByID(ctx, id, hashPass); err != nil {
return err
}
return nil
})
}
func (a *User) GetRoleIDs(ctx context.Context, id string) ([]string, error) {
userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{
UserID: id,
}, schema.UserRoleQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"role_id"},
},
})
if err != nil {
return nil, err
}
return userRoleResult.Data.ToRoleIDs(), nil
}

View File

@ -0,0 +1,99 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type Video struct {
Cache cachex.Cacher
Trans *util.Trans
VideoDAL *dal.Video
}
// Query roles from the data access object based on the provided parameters and options.
func (a *Video) Query(ctx context.Context, params schema.VideoQueryParam) (*schema.VideoQueryResult, error) {
params.Pagination = true
result, err := a.VideoDAL.Query(ctx, params, schema.VideoQueryOptions{
QueryOptions: util.QueryOptions{
OrderFields: []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
},
},
})
if err != nil {
return nil, err
}
return result, nil
}
// Get the specified role from the data access object.
func (a *Video) Get(ctx context.Context, id string) (*schema.Video, error) {
video, err := a.VideoDAL.Get(ctx, id)
if err != nil {
return nil, err
} else if video == nil {
return nil, errors.NotFound("", "Banner not found")
}
return video, nil
}
// Create a new role in the data access object.
func (a *Video) Create(ctx context.Context, formItem *schema.VideoForm) (*schema.Video, error) {
video := &schema.Video{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(video); err != nil {
return nil, err
}
if err := a.VideoDAL.Create(ctx, video); err != nil {
return nil, err
}
return video, nil
}
// Update the specified role in the data access object.
func (a *Video) Update(ctx context.Context, id string, formItem *schema.VideoForm) error {
video, err := a.VideoDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(video); err != nil {
return err
}
video.UpdatedAt = time.Now()
if err := a.VideoDAL.Update(ctx, video); err != nil {
return err
}
return nil
}
// Delete the specified role from the data access object.
func (a *Video) Delete(ctx context.Context, id string) error {
exists, err := a.VideoDAL.Exists(ctx, id)
if err != nil {
return err
} else if !exists {
return errors.NotFound("", "Role not found")
}
if err := a.VideoDAL.Delete(ctx, id); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,63 @@
package biz
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role management for RBAC
type WebSite struct {
Cache cachex.Cacher
Trans *util.Trans
WebSiteDAL *dal.WebSite
}
// Query roles from the data access object based on the provided parameters and options.
func (a *WebSite) Query(ctx context.Context) (*schema.WebSite, error) {
result, err := a.WebSiteDAL.Query(ctx)
if err != nil {
return nil, err
}
return result, nil
}
// Create a new role in the data access object.
func (a *WebSite) Create(ctx context.Context, formItem *schema.WebSiteForm) (*schema.WebSite, error) {
item := &schema.WebSite{
ID: util.NewXID(),
CreatedAt: time.Now(),
}
if err := formItem.FillTo(item); err != nil {
return nil, err
}
if err := a.WebSiteDAL.Create(ctx, item); err != nil {
return nil, err
}
return item, nil
}
// Update the specified role in the data access object.
func (a *WebSite) Update(ctx context.Context, id string, formItem *schema.WebSiteForm) error {
article, err := a.WebSiteDAL.Get(ctx, id)
if err != nil {
return err
}
if err := formItem.FillTo(article); err != nil {
return err
}
article.UpdatedAt = time.Now()
if err := a.WebSiteDAL.Update(ctx, article); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,222 @@
package rbac
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/logging"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/casbin/casbin/v2"
"go.uber.org/zap"
)
// Load rbac permissions to casbin
type Casbinx struct {
enforcer *atomic.Value `wire:"-"`
ticker *time.Ticker `wire:"-"`
Cache cachex.Cacher
MenuDAL *dal.Menu
MenuResourceDAL *dal.MenuResource
RoleDAL *dal.Role
}
func (a *Casbinx) GetEnforcer() *casbin.Enforcer {
if v := a.enforcer.Load(); v != nil {
return v.(*casbin.Enforcer)
}
return nil
}
type policyQueueItem struct {
RoleID string
Resources schema.MenuResources
}
func (a *Casbinx) Load(ctx context.Context) error {
if config.C.Middleware.Casbin.Disable {
return nil
}
a.enforcer = new(atomic.Value)
if err := a.load(ctx); err != nil {
return err
}
go a.autoLoad(ctx)
return nil
}
func (a *Casbinx) load(ctx context.Context) error {
start := time.Now()
roleResult, err := a.RoleDAL.Query(ctx, schema.RoleQueryParam{
Status: schema.RoleStatusEnabled,
}, schema.RoleQueryOptions{
QueryOptions: util.QueryOptions{SelectFields: []string{"id"}},
})
if err != nil {
return err
} else if len(roleResult.Data) == 0 {
return nil
}
var resCount int32
queue := make(chan *policyQueueItem, len(roleResult.Data))
threadNum := config.C.Middleware.Casbin.LoadThread
lock := new(sync.Mutex)
buf := new(bytes.Buffer)
wg := new(sync.WaitGroup)
wg.Add(threadNum)
for i := 0; i < threadNum; i++ {
go func() {
defer wg.Done()
ibuf := new(bytes.Buffer)
for item := range queue {
for _, res := range item.Resources {
_, _ = ibuf.WriteString(fmt.Sprintf("p, %s, %s, %s \n", item.RoleID, res.Path, res.Method))
}
}
lock.Lock()
_, _ = buf.Write(ibuf.Bytes())
lock.Unlock()
}()
}
for _, item := range roleResult.Data {
resources, err := a.queryRoleResources(ctx, item.ID)
if err != nil {
logging.Context(ctx).Error("Failed to query role resources", zap.Error(err))
continue
}
atomic.AddInt32(&resCount, int32(len(resources)))
queue <- &policyQueueItem{
RoleID: item.ID,
Resources: resources,
}
}
close(queue)
wg.Wait()
if buf.Len() > 0 {
policyFile := filepath.Join(config.C.General.WorkDir, config.C.Middleware.Casbin.GenPolicyFile)
_ = os.Rename(policyFile, policyFile+".bak")
_ = os.MkdirAll(filepath.Dir(policyFile), 0755)
if err := os.WriteFile(policyFile, buf.Bytes(), 0666); err != nil {
logging.Context(ctx).Error("Failed to write policy file", zap.Error(err))
return err
}
// set readonly
_ = os.Chmod(policyFile, 0444)
modelFile := filepath.Join(config.C.General.WorkDir, config.C.Middleware.Casbin.ModelFile)
e, err := casbin.NewEnforcer(modelFile, policyFile)
if err != nil {
logging.Context(ctx).Error("Failed to create casbin enforcer", zap.Error(err))
return err
}
e.EnableLog(config.C.IsDebug())
a.enforcer.Store(e)
}
logging.Context(ctx).Info("Casbin load policy",
zap.Duration("cost", time.Since(start)),
zap.Int("roles", len(roleResult.Data)),
zap.Int32("resources", resCount),
zap.Int("bytes", buf.Len()),
)
return nil
}
func (a *Casbinx) queryRoleResources(ctx context.Context, roleID string) (schema.MenuResources, error) {
menuResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{
RoleID: roleID,
Status: schema.MenuStatusEnabled,
}, schema.MenuQueryOptions{
QueryOptions: util.QueryOptions{
SelectFields: []string{"id", "parent_id", "parent_path"},
},
})
if err != nil {
return nil, err
} else if len(menuResult.Data) == 0 {
return nil, nil
}
menuIDs := make([]string, 0, len(menuResult.Data))
menuIDMapper := make(map[string]struct{})
for _, item := range menuResult.Data {
if _, ok := menuIDMapper[item.ID]; ok {
continue
}
menuIDs = append(menuIDs, item.ID)
menuIDMapper[item.ID] = struct{}{}
if pp := item.ParentPath; pp != "" {
for _, pid := range strings.Split(pp, util.TreePathDelimiter) {
if pid == "" {
continue
}
if _, ok := menuIDMapper[pid]; ok {
continue
}
menuIDs = append(menuIDs, pid)
menuIDMapper[pid] = struct{}{}
}
}
}
menuResourceResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{
MenuIDs: menuIDs,
})
if err != nil {
return nil, err
}
return menuResourceResult.Data, nil
}
func (a *Casbinx) autoLoad(ctx context.Context) {
var lastUpdated int64
a.ticker = time.NewTicker(time.Duration(config.C.Middleware.Casbin.AutoLoadInterval) * time.Second)
for range a.ticker.C {
val, ok, err := a.Cache.Get(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin)
if err != nil {
logging.Context(ctx).Error("Failed to get cache", zap.Error(err), zap.String("key", config.CacheKeyForSyncToCasbin))
continue
} else if !ok {
continue
}
updated, err := strconv.ParseInt(val, 10, 64)
if err != nil {
logging.Context(ctx).Error("Failed to parse cache value", zap.Error(err), zap.String("val", val))
continue
}
if lastUpdated < updated {
if err := a.load(ctx); err != nil {
logging.Context(ctx).Error("Failed to load casbin policy", zap.Error(err))
} else {
lastUpdated = updated
}
}
}
}
func (a *Casbinx) Release(ctx context.Context) error {
if a.ticker != nil {
a.ticker.Stop()
}
return nil
}

View File

@ -0,0 +1,89 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetArticleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Article))
}
type Article struct {
DB *gorm.DB
}
// Query roles from the database based on the provided parameters and options.
func (a *Article) Query(ctx context.Context, params schema.ArticleQueryParam, opts ...schema.ArticleQueryOptions) (*schema.ArticleQueryResult, error) {
var opt schema.ArticleQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetArticleDB(ctx, a.DB)
if v := params.LikeTitle; len(v) > 0 {
db = db.Where("title LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
if v := params.Typer; len(v) > 0 {
db = db.Where("type = ?", v)
}
var list schema.Articles
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.ArticleQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified role from the database.
func (a *Article) Get(ctx context.Context, id string, opts ...schema.ArticleQueryOptions) (*schema.Article, error) {
var opt schema.ArticleQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Article)
ok, err := util.FindOne(ctx, GetArticleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified role exists in the database.
func (a *Article) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetArticleDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// Create a new role.
func (a *Article) Create(ctx context.Context, item *schema.Article) error {
result := GetArticleDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified role in the database.
func (a *Article) Update(ctx context.Context, item *schema.Article) error {
result := GetArticleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified role from the database.
func (a *Article) Delete(ctx context.Context, id string) error {
result := GetArticleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Article))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,88 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetBannerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Banner))
}
type Banner struct {
DB *gorm.DB
}
// Query roles from the database based on the provided parameters and options.
func (a *Banner) Query(ctx context.Context, params schema.BannerQueryParam, opts ...schema.BannerQueryOptions) (*schema.BannerQueryResult, error) {
var opt schema.BannerQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetBannerDB(ctx, a.DB)
if v := params.LikeName; len(v) > 0 {
db = db.Where("name LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
var list schema.Banners
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.BannerQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified role from the database.
func (a *Banner) Get(ctx context.Context, id string, opts ...schema.BannerQueryOptions) (*schema.Banner, error) {
var opt schema.BannerQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Banner)
ok, err := util.FindOne(ctx, GetBannerDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified role exists in the database.
func (a *Banner) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetBannerDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// Create a new role.
func (a *Banner) Create(ctx context.Context, item *schema.Banner) error {
result := GetBannerDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified role in the database.
func (a *Banner) Update(ctx context.Context, item *schema.Banner) error {
result := GetBannerDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified role from the database.
func (a *Banner) Delete(ctx context.Context, id string) error {
result := GetBannerDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Banner))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,83 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetJobDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Job))
}
type Job struct {
DB *gorm.DB
}
func (a *Job) Query(ctx context.Context, params schema.JobQueryParam, opts ...schema.JobQueryOptions) (*schema.JobQueryResult, error) {
var opt schema.JobQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetJobDB(ctx, a.DB)
if v := params.LikeTitle; len(v) > 0 {
db = db.Where("title LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
if v := params.JobAreaID; len(v) > 0 {
db = db.Where("jobAreaId = ?", v)
}
var list schema.Jobs
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.JobQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
func (a *Job) Get(ctx context.Context, id string, opts ...schema.JobQueryOptions) (*schema.Job, error) {
var opt schema.JobQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Job)
ok, err := util.FindOne(ctx, GetJobDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *Job) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetJobDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *Job) Create(ctx context.Context, item *schema.Job) error {
result := GetJobDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *Job) Update(ctx context.Context, item *schema.Job) error {
result := GetJobDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
func (a *Job) Delete(ctx context.Context, id string) error {
result := GetJobDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Job))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,58 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetJobAreaDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.JobArea))
}
type JobArea struct {
DB *gorm.DB
}
func (a *JobArea) Query(ctx context.Context) (*[]schema.JobArea, error) {
var list []schema.JobArea
tx := GetJobAreaDB(ctx, a.DB).Find(&list)
if tx.Error != nil {
return nil, errors.WithStack(tx.Error)
}
return &list, nil
}
func (a *JobArea) Get(ctx context.Context, id string) (*schema.JobArea, error) {
item := new(schema.JobArea)
ok, err := util.FindOne(ctx, GetJobAreaDB(ctx, a.DB).Where("id=?", id), util.QueryOptions{}, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *JobArea) Exists(ctx context.Context, name string) (bool, error) {
ok, err := util.Exists(ctx, GetJobAreaDB(ctx, a.DB).Where("name =?", name))
return ok, errors.WithStack(err)
}
func (a *JobArea) Create(ctx context.Context, item *schema.JobArea) error {
result := GetJobAreaDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *JobArea) Update(ctx context.Context, item *schema.JobArea) error {
result := GetJobAreaDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
func (a *JobArea) Delete(ctx context.Context, id string) error {
result := GetJobAreaDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.JobArea))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,64 @@
package dal
import (
"context"
"fmt"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get logger storage instance
func GetLoggerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Logger))
}
// Logger management
type Logger struct {
DB *gorm.DB
}
// Query loggers from the database based on the provided parameters and options.
func (a *Logger) Query(ctx context.Context, params schema.LoggerQueryParam, opts ...schema.LoggerQueryOptions) (*schema.LoggerQueryResult, error) {
var opt schema.LoggerQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := a.DB.Table(fmt.Sprintf("%s AS a", new(schema.Logger).TableName()))
db = db.Joins(fmt.Sprintf("left join %s b on a.user_id=b.id", new(schema.User).TableName()))
db = db.Select("a.*,b.name as user_name,b.username as login_name")
if v := params.Level; v != "" {
db = db.Where("a.level = ?", v)
}
if v := params.LikeMessage; len(v) > 0 {
db = db.Where("a.message LIKE ?", "%"+v+"%")
}
if v := params.TraceID; v != "" {
db = db.Where("a.trace_id = ?", v)
}
if v := params.LikeUserName; v != "" {
db = db.Where("b.username LIKE ?", "%"+v+"%")
}
if v := params.Tag; v != "" {
db = db.Where("a.tag = ?", v)
}
if start, end := params.StartTime, params.EndTime; start != "" && end != "" {
db = db.Where("a.created_at BETWEEN ? AND ?", start, end)
}
var list schema.Loggers
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.LoggerQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}

View File

@ -0,0 +1,86 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetMemorabiliaDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Memorabilia))
}
type Memorabilia struct {
DB *gorm.DB
}
func (a *Memorabilia) Query(ctx context.Context, params schema.MemorabiliaQueryParam, opts ...schema.MemorabiliaQueryOptions) (*schema.MemorabiliaQueryResult, error) {
var opt schema.MemorabiliaQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetMemorabiliaDB(ctx, a.DB)
if v := params.LikeTitle; len(v) > 0 {
db = db.Where("title LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
if v := params.Year; v > 0 {
db = db.Where("year = ?", v)
}
if v := params.Month; v > 0 {
db = db.Where("month = ?", v)
}
var list schema.Memorabilias
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.MemorabiliaQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
func (a *Memorabilia) Get(ctx context.Context, id string, opts ...schema.MemorabiliaQueryOptions) (*schema.Memorabilia, error) {
var opt schema.MemorabiliaQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Memorabilia)
ok, err := util.FindOne(ctx, GetMemorabiliaDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *Memorabilia) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetMemorabiliaDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *Memorabilia) Create(ctx context.Context, item *schema.Memorabilia) error {
result := GetMemorabiliaDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *Memorabilia) Update(ctx context.Context, item *schema.Memorabilia) error {
result := GetMemorabiliaDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
func (a *Memorabilia) Delete(ctx context.Context, id string) error {
result := GetMemorabiliaDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Memorabilia))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,165 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get menu storage instance
func GetMenuDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Menu))
}
// Menu management for RBAC
type Menu struct {
DB *gorm.DB
}
// Query menus from the database based on the provided parameters and options.
func (a *Menu) Query(ctx context.Context, params schema.MenuQueryParam, opts ...schema.MenuQueryOptions) (*schema.MenuQueryResult, error) {
var opt schema.MenuQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetMenuDB(ctx, a.DB)
if v := params.InIDs; len(v) > 0 {
db = db.Where("id IN ?", v)
}
if v := params.LikeName; len(v) > 0 {
db = db.Where("name LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
if v := params.ParentID; len(v) > 0 {
db = db.Where("parent_id = ?", v)
}
if v := params.ParentPathPrefix; len(v) > 0 {
db = db.Where("parent_path LIKE ?", v+"%")
}
if v := params.UserID; len(v) > 0 {
userRoleQuery := GetUserRoleDB(ctx, a.DB).Where("user_id = ?", v).Select("role_id")
roleMenuQuery := GetRoleMenuDB(ctx, a.DB).Where("role_id IN (?)", userRoleQuery).Select("menu_id")
db = db.Where("id IN (?)", roleMenuQuery)
}
if v := params.RoleID; len(v) > 0 {
roleMenuQuery := GetRoleMenuDB(ctx, a.DB).Where("role_id = ?", v).Select("menu_id")
db = db.Where("id IN (?)", roleMenuQuery)
}
var list schema.Menus
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.MenuQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified menu from the database.
func (a *Menu) Get(ctx context.Context, id string, opts ...schema.MenuQueryOptions) (*schema.Menu, error) {
var opt schema.MenuQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Menu)
ok, err := util.FindOne(ctx, GetMenuDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *Menu) GetByCodeAndParentID(ctx context.Context, code, parentID string, opts ...schema.MenuQueryOptions) (*schema.Menu, error) {
var opt schema.MenuQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Menu)
ok, err := util.FindOne(ctx, GetMenuDB(ctx, a.DB).Where("code=? AND parent_id=?", code, parentID), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// GetByNameAndParentID get the specified menu from the database.
func (a *Menu) GetByNameAndParentID(ctx context.Context, name, parentID string, opts ...schema.MenuQueryOptions) (*schema.Menu, error) {
var opt schema.MenuQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Menu)
ok, err := util.FindOne(ctx, GetMenuDB(ctx, a.DB).Where("name=? AND parent_id=?", name, parentID), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Checks if the specified menu exists in the database.
func (a *Menu) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetMenuDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// Checks if a menu with the specified `code` exists under the specified `parentID` in the database.
func (a *Menu) ExistsCodeByParentID(ctx context.Context, code, parentID string) (bool, error) {
ok, err := util.Exists(ctx, GetMenuDB(ctx, a.DB).Where("code=? AND parent_id=?", code, parentID))
return ok, errors.WithStack(err)
}
// Checks if a menu with the specified `name` exists under the specified `parentID` in the database.
func (a *Menu) ExistsNameByParentID(ctx context.Context, name, parentID string) (bool, error) {
ok, err := util.Exists(ctx, GetMenuDB(ctx, a.DB).Where("name=? AND parent_id=?", name, parentID))
return ok, errors.WithStack(err)
}
// Create a new menu.
func (a *Menu) Create(ctx context.Context, item *schema.Menu) error {
result := GetMenuDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified menu in the database.
func (a *Menu) Update(ctx context.Context, item *schema.Menu) error {
result := GetMenuDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified menu from the database.
func (a *Menu) Delete(ctx context.Context, id string) error {
result := GetMenuDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Menu))
return errors.WithStack(result.Error)
}
// Updates the parent path of the specified menu.
func (a *Menu) UpdateParentPath(ctx context.Context, id, parentPath string) error {
result := GetMenuDB(ctx, a.DB).Where("id=?", id).Update("parent_path", parentPath)
return errors.WithStack(result.Error)
}
// Updates the status of all menus whose parent path starts with the provided parent path.
func (a *Menu) UpdateStatusByParentPath(ctx context.Context, parentPath, status string) error {
result := GetMenuDB(ctx, a.DB).Where("parent_path like ?", parentPath+"%").Update("status", status)
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,101 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get menu resource storage instance
func GetMenuResourceDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.MenuResource))
}
// Menu resource management for RBAC
type MenuResource struct {
DB *gorm.DB
}
// Query menu resources from the database based on the provided parameters and options.
func (a *MenuResource) Query(ctx context.Context, params schema.MenuResourceQueryParam, opts ...schema.MenuResourceQueryOptions) (*schema.MenuResourceQueryResult, error) {
var opt schema.MenuResourceQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetMenuResourceDB(ctx, a.DB)
if v := params.MenuID; len(v) > 0 {
db = db.Where("menu_id = ?", v)
}
if v := params.MenuIDs; len(v) > 0 {
db = db.Where("menu_id IN ?", v)
}
var list schema.MenuResources
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.MenuResourceQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified menu resource from the database.
func (a *MenuResource) Get(ctx context.Context, id string, opts ...schema.MenuResourceQueryOptions) (*schema.MenuResource, error) {
var opt schema.MenuResourceQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.MenuResource)
ok, err := util.FindOne(ctx, GetMenuResourceDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified menu resource exists in the database.
func (a *MenuResource) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetMenuResourceDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// ExistsMethodPathByMenuID checks if the specified menu resource exists in the database.
func (a *MenuResource) ExistsMethodPathByMenuID(ctx context.Context, method, path, menuID string) (bool, error) {
ok, err := util.Exists(ctx, GetMenuResourceDB(ctx, a.DB).Where("method=? AND path=? AND menu_id=?", method, path, menuID))
return ok, errors.WithStack(err)
}
// Create a new menu resource.
func (a *MenuResource) Create(ctx context.Context, item *schema.MenuResource) error {
result := GetMenuResourceDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified menu resource in the database.
func (a *MenuResource) Update(ctx context.Context, item *schema.MenuResource) error {
result := GetMenuResourceDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified menu resource from the database.
func (a *MenuResource) Delete(ctx context.Context, id string) error {
result := GetMenuResourceDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.MenuResource))
return errors.WithStack(result.Error)
}
// Deletes the menu resource by menu id.
func (a *MenuResource) DeleteByMenuID(ctx context.Context, menuID string) error {
result := GetMenuResourceDB(ctx, a.DB).Where("menu_id=?", menuID).Delete(new(schema.MenuResource))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,83 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetProductDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Product))
}
type Product struct {
DB *gorm.DB
}
func (a *Product) Query(ctx context.Context, params schema.ProductQueryParam, opts ...schema.ProductQueryOptions) (*schema.ProductQueryResult, error) {
var opt schema.ProductQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetProductDB(ctx, a.DB)
if v := params.LikeTitle; len(v) > 0 {
db = db.Where("title LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
if v := params.CategoryID; v > 0 {
db = db.Where("categoryId = ?", v)
}
var list schema.Products
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.ProductQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
func (a *Product) Get(ctx context.Context, id string, opts ...schema.ProductQueryOptions) (*schema.Product, error) {
var opt schema.ProductQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Product)
ok, err := util.FindOne(ctx, GetProductDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *Product) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetProductDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *Product) Create(ctx context.Context, item *schema.Product) error {
result := GetProductDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *Product) Update(ctx context.Context, item *schema.Product) error {
result := GetProductDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
func (a *Product) Delete(ctx context.Context, id string) error {
result := GetProductDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Product))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,59 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetProductCategoryDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.ProductCategory))
}
type ProductCategory struct {
DB *gorm.DB
}
func (a *ProductCategory) Query(ctx context.Context) (*[]schema.ProductCategory, error) {
db := GetProductCategoryDB(ctx, a.DB)
var list []schema.ProductCategory
tx := db.Find(&list)
if tx.Error != nil {
return nil, errors.WithStack(tx.Error)
}
return &list, nil
}
func (a *ProductCategory) Get(ctx context.Context, id uint) (*schema.ProductCategory, error) {
item := new(schema.ProductCategory)
tx := GetProductCategoryDB(ctx, a.DB).Where("id=?", id).First(item)
if tx.Error != nil { // 没有找到
return nil, errors.WithStack(tx.Error)
}
return item, nil
}
func (a *ProductCategory) Exists(ctx context.Context, id uint) (bool, error) {
ok, err := util.Exists(ctx, GetProductCategoryDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *ProductCategory) Create(ctx context.Context, item *schema.ProductCategory) error {
result := GetProductCategoryDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *ProductCategory) Update(ctx context.Context, item *schema.ProductCategory) error {
result := GetProductCategoryDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
func (a *ProductCategory) Delete(ctx context.Context, id string) error {
result := GetProductCategoryDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ProductCategory))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,100 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get role storage instance
func GetRoleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Role))
}
// Role management for RBAC
type Role struct {
DB *gorm.DB
}
// Query roles from the database based on the provided parameters and options.
func (a *Role) Query(ctx context.Context, params schema.RoleQueryParam, opts ...schema.RoleQueryOptions) (*schema.RoleQueryResult, error) {
var opt schema.RoleQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetRoleDB(ctx, a.DB)
if v := params.InIDs; len(v) > 0 {
db = db.Where("id IN (?)", v)
}
if v := params.LikeName; len(v) > 0 {
db = db.Where("name LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
if v := params.GtUpdatedAt; v != nil {
db = db.Where("updated_at > ?", v)
}
var list schema.Roles
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.RoleQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified role from the database.
func (a *Role) Get(ctx context.Context, id string, opts ...schema.RoleQueryOptions) (*schema.Role, error) {
var opt schema.RoleQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Role)
ok, err := util.FindOne(ctx, GetRoleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified role exists in the database.
func (a *Role) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetRoleDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *Role) ExistsCode(ctx context.Context, code string) (bool, error) {
ok, err := util.Exists(ctx, GetRoleDB(ctx, a.DB).Where("code=?", code))
return ok, errors.WithStack(err)
}
// Create a new role.
func (a *Role) Create(ctx context.Context, item *schema.Role) error {
result := GetRoleDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified role in the database.
func (a *Role) Update(ctx context.Context, item *schema.Role) error {
result := GetRoleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified role from the database.
func (a *Role) Delete(ctx context.Context, id string) error {
result := GetRoleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Role))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,98 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get role menu storage instance
func GetRoleMenuDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.RoleMenu))
}
// Role permissions for RBAC
type RoleMenu struct {
DB *gorm.DB
}
// Query role menus from the database based on the provided parameters and options.
func (a *RoleMenu) Query(ctx context.Context, params schema.RoleMenuQueryParam, opts ...schema.RoleMenuQueryOptions) (*schema.RoleMenuQueryResult, error) {
var opt schema.RoleMenuQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetRoleMenuDB(ctx, a.DB)
if v := params.RoleID; len(v) > 0 {
db = db.Where("role_id = ?", v)
}
var list schema.RoleMenus
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.RoleMenuQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified role menu from the database.
func (a *RoleMenu) Get(ctx context.Context, id string, opts ...schema.RoleMenuQueryOptions) (*schema.RoleMenu, error) {
var opt schema.RoleMenuQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.RoleMenu)
ok, err := util.FindOne(ctx, GetRoleMenuDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified role menu exists in the database.
func (a *RoleMenu) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetRoleMenuDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// Create a new role menu.
func (a *RoleMenu) Create(ctx context.Context, item *schema.RoleMenu) error {
result := GetRoleMenuDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified role menu in the database.
func (a *RoleMenu) Update(ctx context.Context, item *schema.RoleMenu) error {
result := GetRoleMenuDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified role menu from the database.
func (a *RoleMenu) Delete(ctx context.Context, id string) error {
result := GetRoleMenuDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.RoleMenu))
return errors.WithStack(result.Error)
}
// Deletes role menus by role id.
func (a *RoleMenu) DeleteByRoleID(ctx context.Context, roleID string) error {
result := GetRoleMenuDB(ctx, a.DB).Where("role_id=?", roleID).Delete(new(schema.RoleMenu))
return errors.WithStack(result.Error)
}
// Deletes role menus by menu id.
func (a *RoleMenu) DeleteByMenuID(ctx context.Context, menuID string) error {
result := GetRoleMenuDB(ctx, a.DB).Where("menu_id=?", menuID).Delete(new(schema.RoleMenu))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,81 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetTeamDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Team))
}
type Team struct {
DB *gorm.DB
}
func (a *Team) Query(ctx context.Context, params schema.TeamQueryParam, opts ...schema.TeamQueryOptions) (*schema.TeamQueryResult, error) {
var opt schema.TeamQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetTeamDB(ctx, a.DB)
if v := params.LikeName; len(v) > 0 {
db = db.Where("name LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
var list schema.Teams
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.TeamQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
func (a *Team) Get(ctx context.Context, id string, opts ...schema.TeamQueryOptions) (*schema.Team, error) {
var opt schema.TeamQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Team)
ok, err := util.FindOne(ctx, GetTeamDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *Team) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetTeamDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *Team) Create(ctx context.Context, item *schema.Team) error {
result := GetTeamDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *Team) Update(ctx context.Context, item *schema.Team) error {
result := GetTeamDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
func (a *Team) Delete(ctx context.Context, id string) error {
result := GetTeamDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Team))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,124 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get user storage instance
func GetUserDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.User))
}
// User management for RBAC
type User struct {
DB *gorm.DB
}
// Query users from the database based on the provided parameters and options.
func (a *User) Query(ctx context.Context, params schema.UserQueryParam, opts ...schema.UserQueryOptions) (*schema.UserQueryResult, error) {
var opt schema.UserQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetUserDB(ctx, a.DB)
if v := params.LikeUsername; len(v) > 0 {
db = db.Where("username LIKE ?", "%"+v+"%")
}
if v := params.LikeName; len(v) > 0 {
db = db.Where("name LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
var list schema.Users
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.UserQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified user from the database.
func (a *User) Get(ctx context.Context, id string, opts ...schema.UserQueryOptions) (*schema.User, error) {
var opt schema.UserQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.User)
ok, err := util.FindOne(ctx, GetUserDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *User) GetByUsername(ctx context.Context, username string, opts ...schema.UserQueryOptions) (*schema.User, error) {
var opt schema.UserQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.User)
ok, err := util.FindOne(ctx, GetUserDB(ctx, a.DB).Where("username=?", username), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified user exists in the database.
func (a *User) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetUserDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
func (a *User) ExistsUsername(ctx context.Context, username string) (bool, error) {
ok, err := util.Exists(ctx, GetUserDB(ctx, a.DB).Where("username=?", username))
return ok, errors.WithStack(err)
}
// Create a new user.
func (a *User) Create(ctx context.Context, item *schema.User) error {
result := GetUserDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified user in the database.
func (a *User) Update(ctx context.Context, item *schema.User, selectFields ...string) error {
db := GetUserDB(ctx, a.DB).Where("id=?", item.ID)
if len(selectFields) > 0 {
db = db.Select(selectFields)
} else {
db = db.Select("*").Omit("created_at")
}
result := db.Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified user from the database.
func (a *User) Delete(ctx context.Context, id string) error {
result := GetUserDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.User))
return errors.WithStack(result.Error)
}
func (a *User) UpdatePasswordByID(ctx context.Context, id string, password string) error {
result := GetUserDB(ctx, a.DB).Where("id=?", id).Select("password").Updates(schema.User{Password: password})
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,108 @@
package dal
import (
"context"
"fmt"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
// Get user role storage instance
func GetUserRoleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.UserRole))
}
// User roles for RBAC
type UserRole struct {
DB *gorm.DB
}
// Query user roles from the database based on the provided parameters and options.
func (a *UserRole) Query(ctx context.Context, params schema.UserRoleQueryParam, opts ...schema.UserRoleQueryOptions) (*schema.UserRoleQueryResult, error) {
var opt schema.UserRoleQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := a.DB.Table(fmt.Sprintf("%s AS a", new(schema.UserRole).TableName()))
if opt.JoinRole {
db = db.Joins(fmt.Sprintf("left join %s b on a.role_id=b.id", new(schema.Role).TableName()))
db = db.Select("a.*,b.name as role_name")
}
if v := params.InUserIDs; len(v) > 0 {
db = db.Where("a.user_id IN (?)", v)
}
if v := params.UserID; len(v) > 0 {
db = db.Where("a.user_id = ?", v)
}
if v := params.RoleID; len(v) > 0 {
db = db.Where("a.role_id = ?", v)
}
var list schema.UserRoles
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.UserRoleQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified user role from the database.
func (a *UserRole) Get(ctx context.Context, id string, opts ...schema.UserRoleQueryOptions) (*schema.UserRole, error) {
var opt schema.UserRoleQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.UserRole)
ok, err := util.FindOne(ctx, GetUserRoleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified user role exists in the database.
func (a *UserRole) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetUserRoleDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// Create a new user role.
func (a *UserRole) Create(ctx context.Context, item *schema.UserRole) error {
result := GetUserRoleDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified user role in the database.
func (a *UserRole) Update(ctx context.Context, item *schema.UserRole) error {
result := GetUserRoleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified user role from the database.
func (a *UserRole) Delete(ctx context.Context, id string) error {
result := GetUserRoleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.UserRole))
return errors.WithStack(result.Error)
}
func (a *UserRole) DeleteByUserID(ctx context.Context, userID string) error {
result := GetUserRoleDB(ctx, a.DB).Where("user_id=?", userID).Delete(new(schema.UserRole))
return errors.WithStack(result.Error)
}
func (a *UserRole) DeleteByRoleID(ctx context.Context, roleID string) error {
result := GetUserRoleDB(ctx, a.DB).Where("role_id=?", roleID).Delete(new(schema.UserRole))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,88 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetVideoDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.Video))
}
type Video struct {
DB *gorm.DB
}
// Query roles from the database based on the provided parameters and options.
func (a *Video) Query(ctx context.Context, params schema.VideoQueryParam, opts ...schema.VideoQueryOptions) (*schema.VideoQueryResult, error) {
var opt schema.VideoQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
db := GetVideoDB(ctx, a.DB)
if v := params.LikeTitle; len(v) > 0 {
db = db.Where("title LIKE ?", "%"+v+"%")
}
if v := params.Status; len(v) > 0 {
db = db.Where("status = ?", v)
}
var list schema.Videos
pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list)
if err != nil {
return nil, errors.WithStack(err)
}
queryResult := &schema.VideoQueryResult{
PageResult: pageResult,
Data: list,
}
return queryResult, nil
}
// Get the specified role from the database.
func (a *Video) Get(ctx context.Context, id string, opts ...schema.VideoQueryOptions) (*schema.Video, error) {
var opt schema.VideoQueryOptions
if len(opts) > 0 {
opt = opts[0]
}
item := new(schema.Video)
ok, err := util.FindOne(ctx, GetVideoDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
// Exist checks if the specified role exists in the database.
func (a *Video) Exists(ctx context.Context, id string) (bool, error) {
ok, err := util.Exists(ctx, GetVideoDB(ctx, a.DB).Where("id=?", id))
return ok, errors.WithStack(err)
}
// Create a new role.
func (a *Video) Create(ctx context.Context, item *schema.Video) error {
result := GetVideoDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
// Update the specified role in the database.
func (a *Video) Update(ctx context.Context, item *schema.Video) error {
result := GetVideoDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}
// Delete the specified role from the database.
func (a *Video) Delete(ctx context.Context, id string) error {
result := GetVideoDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Video))
return errors.WithStack(result.Error)
}

View File

@ -0,0 +1,46 @@
package dal
import (
"context"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"gorm.io/gorm"
)
func GetWebSiteDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
return util.GetDB(ctx, defDB).Model(new(schema.WebSite))
}
type WebSite struct {
DB *gorm.DB
}
func (a *WebSite) Get(ctx context.Context, id string) (*schema.WebSite, error) {
item := new(schema.WebSite)
ok, err := util.FindOne(ctx, GetJobDB(ctx, a.DB).Where("id=?", id), util.QueryOptions{}, item)
if err != nil {
return nil, errors.WithStack(err)
} else if !ok {
return nil, nil
}
return item, nil
}
func (a *WebSite) Query(ctx context.Context) (*schema.WebSite, error) {
db := GetWebSiteDB(ctx, a.DB)
var info schema.WebSite
db.First(&info)
return &info, nil
}
func (a *WebSite) Create(ctx context.Context, item *schema.WebSite) error {
result := GetWebSiteDB(ctx, a.DB).Create(item)
return errors.WithStack(result.Error)
}
func (a *WebSite) Update(ctx context.Context, item *schema.WebSite) error {
result := GetWebSiteDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item)
return errors.WithStack(result.Error)
}

217
internal/mods/rbac/main.go Normal file
View File

@ -0,0 +1,217 @@
package rbac
import (
"context"
"path/filepath"
"github.com/gin-gonic/gin"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods/rbac/api"
"github.com/guxuan/hailin_service/internal/mods/rbac/schema"
"github.com/guxuan/hailin_service/pkg/logging"
"go.uber.org/zap"
"gorm.io/gorm"
)
type RBAC struct {
DB *gorm.DB
MenuAPI *api.Menu
RoleAPI *api.Role
UserAPI *api.User
LoginAPI *api.Login
LoggerAPI *api.Logger
BannerAPI *api.Banner
ArticleAPI *api.Article
VideoAPI *api.Video
UploadAPI *api.Upload
JobAPI *api.Job
WebAPI *api.Web
WebSiteAPI *api.WebSite
TeamAPI *api.Team
MemorabiliaAPI *api.Memorabilia
ProductAPI *api.Product
Casbinx *Casbinx
}
func (a *RBAC) AutoMigrate(ctx context.Context) error {
return a.DB.AutoMigrate(
new(schema.Menu),
new(schema.MenuResource),
new(schema.Role),
new(schema.RoleMenu),
new(schema.User),
new(schema.UserRole),
new(schema.Banner),
new(schema.Article),
new(schema.Video),
new(schema.Memorabilia),
new(schema.Job),
new(schema.JobArea),
new(schema.WebSite),
new(schema.Team),
new(schema.Product),
new(schema.ProductCategory),
)
}
func (a *RBAC) Init(ctx context.Context) error {
if config.C.Storage.DB.AutoMigrate {
if err := a.AutoMigrate(ctx); err != nil {
return err
}
}
if err := a.Casbinx.Load(ctx); err != nil {
return err
}
if name := config.C.General.MenuFile; name != "" {
fullPath := filepath.Join(config.C.General.WorkDir, name)
if err := a.MenuAPI.MenuBIZ.InitFromFile(ctx, fullPath); err != nil {
logging.Context(ctx).Error("failed to init menu data", zap.Error(err), zap.String("file", fullPath))
}
}
return nil
}
func (a *RBAC) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error {
captcha := v1.Group("captcha")
{
captcha.GET("id", a.LoginAPI.GetCaptcha)
captcha.GET("image", a.LoginAPI.ResponseCaptcha)
}
v1.POST("login", a.LoginAPI.Login)
v1.POST("upload", a.UploadAPI.SaveFile)
current := v1.Group("current")
{
current.POST("refresh-token", a.LoginAPI.RefreshToken)
current.GET("user", a.LoginAPI.GetUserInfo)
current.GET("menus", a.LoginAPI.QueryMenus)
current.PUT("password", a.LoginAPI.UpdatePassword)
current.PUT("user", a.LoginAPI.UpdateUser)
current.POST("logout", a.LoginAPI.Logout)
}
banner := v1.Group("banners")
{
banner.GET("", a.BannerAPI.Query)
banner.GET(":id", a.BannerAPI.Get)
banner.POST("", a.BannerAPI.Create)
banner.PUT(":id", a.BannerAPI.Update)
banner.DELETE(":id", a.BannerAPI.Delete)
}
job := v1.Group("jobs")
{
job.GET("", a.JobAPI.Query)
job.GET("job_areas", a.JobAPI.QueryJobArea)
job.POST("job_areas", a.JobAPI.CreateJobArea)
job.PUT("job_areas/:id", a.JobAPI.UpdateJobArea)
job.DELETE("job_areas/:id", a.JobAPI.DeleteJobArea)
job.GET(":id", a.JobAPI.Get)
job.POST("", a.JobAPI.Create)
job.PUT(":id", a.JobAPI.Update)
job.DELETE(":id", a.JobAPI.Delete)
}
memorabilia := v1.Group("memorabilias")
{
memorabilia.GET("", a.MemorabiliaAPI.Query)
memorabilia.GET(":id", a.MemorabiliaAPI.Get)
memorabilia.POST("", a.MemorabiliaAPI.Create)
memorabilia.PUT(":id", a.MemorabiliaAPI.Update)
memorabilia.DELETE(":id", a.MemorabiliaAPI.Delete)
}
team := v1.Group("teams")
{
team.GET("", a.TeamAPI.Query)
team.GET(":id", a.TeamAPI.Get)
team.POST("", a.TeamAPI.Create)
team.PUT(":id", a.TeamAPI.Update)
team.DELETE(":id", a.TeamAPI.Delete)
}
webSite := v1.Group("web_site")
{
webSite.GET("", a.WebSiteAPI.Query)
webSite.POST("", a.WebSiteAPI.Create)
webSite.PUT(":id", a.WebSiteAPI.Update)
}
product := v1.Group("products")
{
product.GET("", a.ProductAPI.Query)
product.GET("categorys", a.ProductAPI.QueryCategory)
product.GET(":id", a.ProductAPI.Get)
product.POST("", a.ProductAPI.Create)
product.PUT(":id", a.ProductAPI.Update)
product.DELETE(":id", a.ProductAPI.Delete)
}
article := v1.Group("articles")
{
article.GET("", a.ArticleAPI.Query)
article.GET(":id", a.ArticleAPI.Get)
article.POST("", a.ArticleAPI.Create)
article.PUT(":id", a.ArticleAPI.Update)
article.DELETE(":id", a.ArticleAPI.Delete)
}
video := v1.Group("videos")
{
video.GET("", a.VideoAPI.Query)
video.GET(":id", a.VideoAPI.Get)
video.POST("", a.VideoAPI.Create)
video.PUT(":id", a.VideoAPI.Update)
video.DELETE(":id", a.VideoAPI.Delete)
}
menu := v1.Group("menus")
{
menu.GET("", a.MenuAPI.Query)
menu.GET(":id", a.MenuAPI.Get)
menu.POST("", a.MenuAPI.Create)
menu.PUT(":id", a.MenuAPI.Update)
menu.DELETE(":id", a.MenuAPI.Delete)
}
role := v1.Group("roles")
{
role.GET("", a.RoleAPI.Query)
role.GET(":id", a.RoleAPI.Get)
role.POST("", a.RoleAPI.Create)
role.PUT(":id", a.RoleAPI.Update)
role.DELETE(":id", a.RoleAPI.Delete)
}
user := v1.Group("users")
{
user.GET("", a.UserAPI.Query)
user.GET(":id", a.UserAPI.Get)
user.POST("", a.UserAPI.Create)
user.PUT(":id", a.UserAPI.Update)
user.DELETE(":id", a.UserAPI.Delete)
user.PATCH(":id/reset-pwd", a.UserAPI.ResetPassword)
}
web := v1.Group("web")
{
web.GET("jobs", a.WebAPI.QueryJob)
web.GET("articles", a.WebAPI.QueryArticle)
}
logger := v1.Group("loggers")
{
logger.GET("", a.LoggerAPI.Query)
}
return nil
}
func (a *RBAC) Release(ctx context.Context) error {
if err := a.Casbinx.Release(ctx); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,17 @@
package resp
import "time"
type JobAdminListResp struct {
AreaName string `json:"areaName"`
ID string `json:"id" `
JobAreaID string `json:"jobAreaId"`
Title string `json:"title" `
Introduce string `json:"introduce" `
Duty string `json:"duty" `
Salary string `json:"salary" `
Sequence int `json:"sequence"`
Status string `json:"status" `
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at" `
}

View File

@ -0,0 +1 @@
package resp

View File

@ -0,0 +1,91 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
ArticleStatusDisabled = "disabled"
ArticleStatusEnabled = "enabled"
)
var (
ArticleOrderParams = []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.DESC},
}
ArticleOrderNoCreatedAtParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Article management for RBAC
type Article struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner
Img string `json:"img" gorm:"size:1024"` // Details about banner
Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc)
Link string `json:"link" gorm:"size:1024;"`
Content string `json:"content" gorm:"type:text;comment:详情"`
PushAt string `json:"pushAt" gorm:"size:50;index"`
Typer string `json:"type" gorm:"size:50;not null;index"` // Type of banner (banner, link) // Parent menu``
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *Article) TableName() string {
return config.C.FormatTableName("article")
}
// Defining the query parameters for the `Menu` struct.
type ArticleQueryParam struct {
util.PaginationParam
LikeTitle string `form:"title"` // Display name of menu
Typer string `form:"type" bind:"required,max=50"`
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type ArticleQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type ArticleQueryResult struct {
Data Articles
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Articles []*Article
func (a Articles) Len() int {
return len(a)
}
type ArticleForm struct {
Title string `json:"title" binding:"required,max=128"` // Display name of menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Link string `json:"link" `
Content string `json:"content"`
PushAt string `json:"pushAt"`
Typer string `json:"type" binding:"required,oneof=banner home achievement honor talent_center team news"` // Type of menu (banner, link) // Parent menu``
Img string `json:"img" ` // Details about banner
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
}
func (a *ArticleForm) FillTo(article *Article) error {
article.Title = a.Title
article.PushAt = a.PushAt
article.Sequence = a.Sequence
article.Content = a.Content
article.Img = a.Img
article.Link = a.Link
article.Typer = a.Typer
article.Status = a.Status
return nil
}

View File

@ -0,0 +1,84 @@
package schema
import (
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
"time"
)
const (
BannerStatusDisabled = "disabled"
BannerStatusEnabled = "enabled"
)
var (
BannerOrderParams = []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.DESC},
}
BannerOrderNoCreatedAtParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Menu management for RBAC
type Banner struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Name string `json:"name" gorm:"size:128;not null;index"` // Display name of banner
Img string `json:"img" gorm:"size:1024"` // Details about banner
Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc)
Link string `json:"link" gorm:"size:1024;"`
Typer int `json:"type" gorm:"size:11;not null;index"` // Type of banner (banner, link) // Parent menu``
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *Banner) TableName() string {
return config.C.FormatTableName("banner")
}
// Defining the query parameters for the `Menu` struct.
type BannerQueryParam struct {
util.PaginationParam
LikeName string `form:"name"` // Display name of menu
Typer int `form:"type" bind:"required,max=11"`
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type BannerQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type BannerQueryResult struct {
Data Banners
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Banners []*Banner
func (a Banners) Len() int {
return len(a)
}
type BannerForm struct {
Name string `json:"name" binding:"required,max=128"` // Display name of menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Link string `json:"link" `
Typer int `json:"type" binding:"required"` // Type of menu (banner, link) // Parent menu``
Img string `json:"img" gorm:"size:1024"` // Details about banner
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
}
func (a *BannerForm) FillTo(banner *Banner) error {
banner.Name = a.Name
banner.Sequence = a.Sequence
banner.Img = a.Img
banner.Link = a.Link
banner.Typer = a.Typer
banner.Status = a.Status
return nil
}

View File

@ -0,0 +1,108 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
JobStatusDisabled = "disabled"
JobStatusEnabled = "enabled"
)
var (
JobOrderParams = []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.DESC},
}
JobOrderNoCreatedAtParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Article management for RBAC
type Job struct {
ID string `json:"id" gorm:"size:20;primarykey;"` //主键
JobAreaID string `json:"jobAreaId" gorm:"size:20;not null;index"` //职位地区id
Title string `json:"title" gorm:"size:128;not null;index"` //标题
Introduce string `json:"introduce" gorm:"type:text"` //要求
Duty string `json:"duty" gorm:"type:text"` //职责
Salary string `json:"salary" gorm:"size:50"` //薪资
Sequence int `json:"sequence" gorm:"index;"` //排序
Status string `json:"status" gorm:"size:20;index"` //状态
CreatedAt time.Time `json:"created_at" gorm:"index;"`
UpdatedAt time.Time `json:"updated_at" gorm:"index;"`
}
type JobArea struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Name string `json:"name" gorm:"size:128;not null;index"` // 区域名字
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
type WebJobData struct {
JobAreaTitle string `json:"jobAreaTitle"`
JobList Jobs `json:"jobList"`
}
func (a *Job) TableName() string {
return config.C.FormatTableName("job")
}
func (a *JobArea) TableName() string {
return config.C.FormatTableName("job_area")
}
// Defining the query parameters for the `Menu` struct.
type JobQueryParam struct {
util.PaginationParam
JobAreaID string `form:"jobAreaId"` // Display name of menu
LikeTitle string `form:"title"` // Display name of menu
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type JobQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type JobQueryResult struct {
Data Jobs
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Jobs []*Job
func (a Jobs) Len() int {
return len(a)
}
type JobForm struct {
Title string `json:"title" binding:"required,max=128"` // Display name of menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
JobAreaId string `json:"jobAreaId" `
Introduce string `json:"introduce"`
Duty string `json:"duty" `
Salary string `json:"salary"` // Details about banner
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
}
type JobAreaForm struct {
Name string `json:"name" binding:"required,max=128"` // Display name of menu // Details about banner
Status string `json:"status" binding:"required,oneof=disabled enabled"`
}
func (a *JobForm) FillTo(job *Job) error {
job.Title = a.Title
job.JobAreaID = a.JobAreaId
job.Sequence = a.Sequence
job.Introduce = a.Introduce
job.Duty = a.Duty
job.Salary = a.Salary
job.Status = a.Status
return nil
}

View File

@ -0,0 +1,53 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
// Logger management
type Logger struct {
ID string `gorm:"size:20;primaryKey;" json:"id"` // Unique ID
Level string `gorm:"size:20;index;" json:"level"` // Log level
TraceID string `gorm:"size:64;index;" json:"trace_id"` // Trace ID
UserID string `gorm:"size:20;index;" json:"user_id"` // User ID
Tag string `gorm:"size:32;index;" json:"tag"` // Log tag
Message string `gorm:"size:1024;" json:"message"` // Log message
Stack string `gorm:"type:text;" json:"stack"` // Error stack
Data string `gorm:"type:text;" json:"data"` // Log data
CreatedAt time.Time `gorm:"index;" json:"created_at"` // Create time
LoginName string `json:"login_name" gorm:"<-:false;-:migration;"` // From User.Username
UserName string `json:"user_name" gorm:"<-:false;-:migration;"` // From User.Name
}
func (a *Logger) TableName() string {
return config.C.FormatTableName("logger")
}
// Defining the query parameters for the `Logger` struct.
type LoggerQueryParam struct {
util.PaginationParam
Level string `form:"level"` // Log level
TraceID string `form:"traceID"` // Trace ID
LikeUserName string `form:"userName"` // User Name
Tag string `form:"tag"` // Log tag
LikeMessage string `form:"message"` // Log message
StartTime string `form:"startTime"` // Start time
EndTime string `form:"endTime"` // End time
}
// Defining the query options for the `Logger` struct.
type LoggerQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Logger` struct.
type LoggerQueryResult struct {
Data Loggers
PageResult *util.PaginationResult
}
// Defining the slice of `Logger` struct.
type Loggers []*Logger

View File

@ -0,0 +1,38 @@
package schema
import "strings"
type Captcha struct {
CaptchaID string `json:"captcha_id"` // Captcha ID
}
type LoginForm struct {
Username string `json:"username" binding:"required"` // Login name
Password string `json:"password" binding:"required"` // Login password (md5 hash)
CaptchaID string `json:"captcha_id" ` // Captcha verify id
CaptchaCode string `json:"captcha_code" ` // Captcha verify code
}
func (a *LoginForm) Trim() *LoginForm {
a.Username = strings.TrimSpace(a.Username)
a.CaptchaCode = strings.TrimSpace(a.CaptchaCode)
return a
}
type UpdateLoginPassword struct {
OldPassword string `json:"old_password" binding:"required"` // Old password (md5 hash)
NewPassword string `json:"new_password" binding:"required"` // New password (md5 hash)
}
type LoginToken struct {
AccessToken string `json:"access_token"` // Access token (JWT)
TokenType string `json:"token_type"` // Token type (Usage: Authorization=${token_type} ${access_token})
ExpiresAt int64 `json:"expires_at"` // Expired time (Unit: second)
}
type UpdateCurrentUser struct {
Name string `json:"name" binding:"required,max=64"` // Name of user
Phone string `json:"phone" binding:"max=32"` // Phone number of user
Email string `json:"email" binding:"max=128"` // Email of user
Remark string `json:"remark" binding:"max=1024"` // Remark of user
}

View File

@ -0,0 +1,79 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
MemorabiliaStatusDisabled = "disabled"
MemorabiliaStatusEnabled = "enabled"
)
var (
MemorabiliaOrderParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Article management for RBAC
type Memorabilia struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner
Sequence int `json:"sequence" gorm:"index;default:0"`
Year int `json:"year" gorm:"size:10;not null;index"` // Type of banner (banner, link) // Parent menu``
Month int `json:"month" gorm:"size:10;not null;index"` // Type of banner (banner, link) // Parent menu``
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *Memorabilia) TableName() string {
return config.C.FormatTableName("memorabilia")
}
// Defining the query parameters for the `Menu` struct.
type MemorabiliaQueryParam struct {
util.PaginationParam
LikeTitle string `form:"title"` // Display name of menu
Month int `form:"month"`
Year int `form:"year"`
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type MemorabiliaQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type MemorabiliaQueryResult struct {
Data Memorabilias
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Memorabilias []*Memorabilia
func (a Memorabilias) Len() int {
return len(a)
}
type MemorabiliaForm struct {
Title string `json:"title" binding:"required,max=128"` // Display name of menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Year int `json:"year"`
Month int `json:"month"`
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
}
func (a *MemorabiliaForm) FillTo(memorabilia *Memorabilia) error {
memorabilia.Title = a.Title
memorabilia.Month = a.Month
memorabilia.Sequence = a.Sequence
memorabilia.Year = a.Year
memorabilia.Status = a.Status
return nil
}

View File

@ -0,0 +1,178 @@
package schema
import (
"encoding/json"
"strings"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
MenuStatusDisabled = "disabled"
MenuStatusEnabled = "enabled"
)
var (
MenusOrderParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
{Field: "created_at", Direction: util.DESC},
}
)
// Menu management for RBAC
type Menu struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Code string `json:"code" gorm:"size:32;index;"` // Code of menu (unique for each level)
Name string `json:"name" gorm:"size:128;index"` // Display name of menu
Description string `json:"description" gorm:"size:1024"` // Details about menu
Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc)
Type string `json:"type" gorm:"size:20;index"` // Type of menu (page, button)
Path string `json:"path" gorm:"size:255;"` // Access path of menu
Properties string `json:"properties" gorm:"type:text;"` // Properties of menu (JSON)
Status string `json:"status" gorm:"size:20;index"` // Status of menu (enabled, disabled)
ParentID string `json:"parent_id" gorm:"size:20;index;"` // Parent ID (From Menu.ID)
ParentPath string `json:"parent_path" gorm:"size:255;index;"` // Parent path (split by .)
Children *Menus `json:"children" gorm:"-"` // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
Resources MenuResources `json:"resources" gorm:"-"` // Resources of menu
}
func (a *Menu) TableName() string {
return config.C.FormatTableName("menu")
}
// Defining the query parameters for the `Menu` struct.
type MenuQueryParam struct {
util.PaginationParam
CodePath string `form:"code"` // Code path (like xxx.xxx.xxx)
LikeName string `form:"name"` // Display name of menu
IncludeResources bool `form:"includeResources"` // Include resources
InIDs []string `form:"-"` // Include menu IDs
Status string `form:"-"` // Status of menu (disabled, enabled)
ParentID string `form:"-"` // Parent ID (From Menu.ID)
ParentPathPrefix string `form:"-"` // Parent path (split by .)
UserID string `form:"-"` // User ID
RoleID string `form:"-"` // Role ID
}
// Defining the query options for the `Menu` struct.
type MenuQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type MenuQueryResult struct {
Data Menus
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Menus []*Menu
func (a Menus) Len() int {
return len(a)
}
func (a Menus) Less(i, j int) bool {
if a[i].Sequence == a[j].Sequence {
return a[i].CreatedAt.Unix() > a[j].CreatedAt.Unix()
}
return a[i].Sequence > a[j].Sequence
}
func (a Menus) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a Menus) ToMap() map[string]*Menu {
m := make(map[string]*Menu)
for _, item := range a {
m[item.ID] = item
}
return m
}
func (a Menus) SplitParentIDs() []string {
parentIDs := make([]string, 0, len(a))
idMapper := make(map[string]struct{})
for _, item := range a {
if _, ok := idMapper[item.ID]; ok {
continue
}
idMapper[item.ID] = struct{}{}
if pp := item.ParentPath; pp != "" {
for _, pid := range strings.Split(pp, util.TreePathDelimiter) {
if pid == "" {
continue
}
if _, ok := idMapper[pid]; ok {
continue
}
parentIDs = append(parentIDs, pid)
idMapper[pid] = struct{}{}
}
}
}
return parentIDs
}
func (a Menus) ToTree() Menus {
var list Menus
m := a.ToMap()
for _, item := range a {
if item.ParentID == "" {
list = append(list, item)
continue
}
if parent, ok := m[item.ParentID]; ok {
if parent.Children == nil {
children := Menus{item}
parent.Children = &children
continue
}
*parent.Children = append(*parent.Children, item)
}
}
return list
}
// Defining the data structure for creating a `Menu` struct.
type MenuForm struct {
Code string `json:"code" binding:"required,max=32"` // Code of menu (unique for each level)
Name string `json:"name" binding:"required,max=128"` // Display name of menu
Description string `json:"description"` // Details about menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Type string `json:"type" binding:"required,oneof=page button"` // Type of menu (page, button)
Path string `json:"path"` // Access path of menu
Properties string `json:"properties"` // Properties of menu (JSON)
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
ParentID string `json:"parent_id"` // Parent ID (From Menu.ID)
Resources MenuResources `json:"resources"` // Resources of menu
}
// A validation function for the `MenuForm` struct.
func (a *MenuForm) Validate() error {
if v := a.Properties; v != "" {
if !json.Valid([]byte(v)) {
return errors.BadRequest("", "invalid properties")
}
}
return nil
}
func (a *MenuForm) FillTo(menu *Menu) error {
menu.Code = a.Code
menu.Name = a.Name
menu.Description = a.Description
menu.Sequence = a.Sequence
menu.Type = a.Type
menu.Path = a.Path
menu.Properties = a.Properties
menu.Status = a.Status
menu.ParentID = a.ParentID
return nil
}

View File

@ -0,0 +1,56 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
// Menu resource management for RBAC
type MenuResource struct {
ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID
MenuID string `json:"menu_id" gorm:"size:20;index"` // From Menu.ID
Method string `json:"method" gorm:"size:20;"` // HTTP method
Path string `json:"path" gorm:"size:255;"` // API request path (e.g. /api/v1/users/:id)
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *MenuResource) TableName() string {
return config.C.FormatTableName("menu_resource")
}
// Defining the query parameters for the `MenuResource` struct.
type MenuResourceQueryParam struct {
util.PaginationParam
MenuID string `form:"-"` // From Menu.ID
MenuIDs []string `form:"-"` // From Menu.ID
}
// Defining the query options for the `MenuResource` struct.
type MenuResourceQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `MenuResource` struct.
type MenuResourceQueryResult struct {
Data MenuResources
PageResult *util.PaginationResult
}
// Defining the slice of `MenuResource` struct.
type MenuResources []*MenuResource
// Defining the data structure for creating a `MenuResource` struct.
type MenuResourceForm struct {
}
// A validation function for the `MenuResourceForm` struct.
func (a *MenuResourceForm) Validate() error {
return nil
}
func (a *MenuResourceForm) FillTo(menuResource *MenuResource) error {
return nil
}

View File

@ -0,0 +1,114 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
ProductStatusDisabled = "disabled"
ProudctStatusEnabled = "enabled"
)
var (
ProductOrderParams = []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.DESC},
}
ProductOrderNoCreatedAtParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Article management for RBAC
type Product struct {
ID string `json:"id" gorm:"size:20;primarykey;"`
CategoryID uint `json:"categoryID" gorm:"index;"`
Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner
Images []string `json:"images" gorm:"serializer:json"`
Code string `json:"code" gorm:"size:50"` // Details about banner
Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc)
Compose string `json:"compose" gorm:"size:1024;"`
Target string `json:"target" gorm:"size:1024"`
Standard *[]ProductStandard `json:"standard" gorm:"serializer:json"`
Feature string `json:"feature" gorm:"size:2048"` // Type of banner (banner, link) // Parent menu``
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
type ProductStandard struct {
Label string
Value string
}
type ProductCategory struct {
ID uint `json:"id" gorm:"primaryKey"`
Label string `json:"label" gorm:"size:128;not null;index"` // Display name of banner
ParentID uint `json:"parentID" gorm:"size:50;not null;index"` // Type of banner (banner, link) // Parent menu``
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *Product) TableName() string {
return config.C.FormatTableName("product")
}
func (a *ProductCategory) TableName() string {
return config.C.FormatTableName("product_category")
}
// Defining the query parameters for the `Menu` struct.
type ProductQueryParam struct {
util.PaginationParam
LikeTitle string `form:"title"` // Display name of menu
CategoryID uint `form:"categoryId"`
Code string `form:"code" `
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type ProductQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type ProductQueryResult struct {
Data Products
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Products []*Product
func (a Products) Len() int {
return len(a)
}
type ProductForm struct {
CategoryID uint `json:"categoryID" binding:"required,max=11"`
Title string `json:"title" binding:"required,max=128"` // Display name of menu
Images []string `json:"images"`
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Code string `json:"code" `
Compose string `json:"compose"`
Feature string `json:"feature"`
Target string `json:"target" ` // Type of menu (banner, link) // Parent menu``
Standard *[]ProductStandard `json:"standard" ` // Details about banner
Status string `json:"status" binding:"required,oneof=disabled enabled"`
}
func (a *ProductForm) FillTo(product *Product) error {
product.Title = a.Title
product.Compose = a.Compose
product.Sequence = a.Sequence
product.Code = a.Code
product.CategoryID = a.CategoryID
product.Standard = a.Standard
product.Images = a.Images
product.Feature = a.Feature
product.Target = a.Target
product.Status = a.Status
return nil
}

View File

@ -0,0 +1,80 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
RoleStatusEnabled = "enabled" // Enabled
RoleStatusDisabled = "disabled" // Disabled
RoleResultTypeSelect = "select" // Select
)
// Role management for RBAC
type Role struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Code string `json:"code" gorm:"size:32;index;"` // Code of role (unique)
Name string `json:"name" gorm:"size:128;index"` // Display name of role
Description string `json:"description" gorm:"size:1024"` // Details about role
Sequence int `json:"sequence" gorm:"index"` // Sequence for sorting
Status string `json:"status" gorm:"size:20;index"` // Status of role (disabled, enabled)
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
Menus RoleMenus `json:"menus" gorm:"-"` // Role menu list
}
func (a *Role) TableName() string {
return config.C.FormatTableName("role")
}
// Defining the query parameters for the `Role` struct.
type RoleQueryParam struct {
util.PaginationParam
LikeName string `form:"name"` // Display name of role
Status string `form:"status" binding:"oneof=disabled enabled ''"` // Status of role (disabled, enabled)
ResultType string `form:"resultType"` // Result type (options: select)
InIDs []string `form:"-"` // ID list
GtUpdatedAt *time.Time `form:"-"` // Update time is greater than
}
// Defining the query options for the `Role` struct.
type RoleQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Role` struct.
type RoleQueryResult struct {
Data Roles
PageResult *util.PaginationResult
}
// Defining the slice of `Role` struct.
type Roles []*Role
// Defining the data structure for creating a `Role` struct.
type RoleForm struct {
Code string `json:"code" binding:"required,max=32"` // Code of role (unique)
Name string `json:"name" binding:"required,max=128"` // Display name of role
Description string `json:"description"` // Details about role
Sequence int `json:"sequence"` // Sequence for sorting
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of role (enabled, disabled)
Menus RoleMenus `json:"menus"` // Role menu list
}
// A validation function for the `RoleForm` struct.
func (a *RoleForm) Validate() error {
return nil
}
func (a *RoleForm) FillTo(role *Role) error {
role.Code = a.Code
role.Name = a.Name
role.Description = a.Description
role.Sequence = a.Sequence
role.Status = a.Status
return nil
}

View File

@ -0,0 +1,54 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
// Role permissions for RBAC
type RoleMenu struct {
ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID
RoleID string `json:"role_id" gorm:"size:20;index"` // From Role.ID
MenuID string `json:"menu_id" gorm:"size:20;index"` // From Menu.ID
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *RoleMenu) TableName() string {
return config.C.FormatTableName("role_menu")
}
// Defining the query parameters for the `RoleMenu` struct.
type RoleMenuQueryParam struct {
util.PaginationParam
RoleID string `form:"-"` // From Role.ID
}
// Defining the query options for the `RoleMenu` struct.
type RoleMenuQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `RoleMenu` struct.
type RoleMenuQueryResult struct {
Data RoleMenus
PageResult *util.PaginationResult
}
// Defining the slice of `RoleMenu` struct.
type RoleMenus []*RoleMenu
// Defining the data structure for creating a `RoleMenu` struct.
type RoleMenuForm struct {
}
// A validation function for the `RoleMenuForm` struct.
func (a *RoleMenuForm) Validate() error {
return nil
}
func (a *RoleMenuForm) FillTo(roleMenu *RoleMenu) error {
return nil
}

View File

@ -0,0 +1,84 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
TeamStatusDisabled = "disabled"
TeamStatusEnabled = "enabled"
)
var (
TeamOrderParams = []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.DESC},
}
TeamOrderNoCreatedAtParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Article management for RBAC
type Team struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Name string `json:"name" gorm:"size:128;not null;index"` // Display name of banner
Img string `json:"img" gorm:"size:1024"` // Details about banner
Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc)
Area string `json:"area" gorm:"size:1024"`
Rank *[]string `json:"rank" gorm:"serializer:json"` // Type of banner (banner, link) // Parent menu``
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *Team) TableName() string {
return config.C.FormatTableName("team")
}
// Defining the query parameters for the `Menu` struct.
type TeamQueryParam struct {
util.PaginationParam
LikeName string `form:"name"` // Display name of menu
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type TeamQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type TeamQueryResult struct {
Data Teams
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Teams []*Team
func (a Teams) Len() int {
return len(a)
}
type TeamForm struct {
Name string `json:"name" binding:"required,max=128"` // Display name of menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Area string `json:"area" `
Rank *[]string `json:"rank"` // Type of banner (ban
Img string `json:"img" ` // Details about banner
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
}
func (a *TeamForm) FillTo(team *Team) error {
team.Name = a.Name
team.Area = a.Area
team.Sequence = a.Sequence
team.Img = a.Img
team.Rank = a.Rank
team.Status = a.Status
return nil
}

View File

@ -0,0 +1,105 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/crypto/hash"
"github.com/guxuan/hailin_service/pkg/errors"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/go-playground/validator/v10"
)
const (
UserStatusActivated = "activated"
UserStatusFreezed = "freezed"
)
// User management for RBAC
type User struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Username string `json:"username" gorm:"size:64;index"` // Username for login
Name string `json:"name" gorm:"size:64;index"` // Name of user
Password string `json:"-" gorm:"size:64;"` // Password for login (encrypted)
Phone string `json:"phone" gorm:"size:32;"` // Phone number of user
Email string `json:"email" gorm:"size:128;"` // Email of user
Remark string `json:"remark" gorm:"size:1024;"` // Remark of user
Status string `json:"status" gorm:"size:20;index"` // Status of user (activated, freezed)
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
Roles UserRoles `json:"roles" gorm:"-"` // Roles of user
}
func (a *User) TableName() string {
return config.C.FormatTableName("user")
}
// Defining the query parameters for the `User` struct.
type UserQueryParam struct {
util.PaginationParam
LikeUsername string `form:"username"` // Username for login
LikeName string `form:"name"` // Name of user
Status string `form:"status" binding:"oneof=activated freezed ''"` // Status of user (activated, freezed)
}
// Defining the query options for the `User` struct.
type UserQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `User` struct.
type UserQueryResult struct {
Data Users
PageResult *util.PaginationResult
}
// Defining the slice of `User` struct.
type Users []*User
func (a Users) ToIDs() []string {
var ids []string
for _, item := range a {
ids = append(ids, item.ID)
}
return ids
}
// Defining the data structure for creating a `User` struct.
type UserForm struct {
Username string `json:"username" binding:"required,max=64"` // Username for login
Name string `json:"name" binding:"required,max=64"` // Name of user
Password string `json:"password" binding:"max=64"` // Password for login (md5 hash)
Phone string `json:"phone" binding:"max=32"` // Phone number of user
Email string `json:"email" binding:"max=128"` // Email of user
Remark string `json:"remark" binding:"max=1024"` // Remark of user
Status string `json:"status" binding:"required,oneof=activated freezed"` // Status of user (activated, freezed)
Roles UserRoles `json:"roles" binding:"required"` // Roles of user
}
// A validation function for the `UserForm` struct.
func (a *UserForm) Validate() error {
if a.Email != "" && validator.New().Var(a.Email, "email") != nil {
return errors.BadRequest("", "Invalid email address")
}
return nil
}
// Convert `UserForm` to `User` object.
func (a *UserForm) FillTo(user *User) error {
user.Username = a.Username
user.Name = a.Name
user.Phone = a.Phone
user.Email = a.Email
user.Remark = a.Remark
user.Status = a.Status
if pass := a.Password; pass != "" {
hashPass, err := hash.GeneratePassword(pass)
if err != nil {
return errors.BadRequest("", "Failed to generate hash password: %s", err.Error())
}
user.Password = hashPass
}
return nil
}

View File

@ -0,0 +1,74 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
// User roles for RBAC
type UserRole struct {
ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID
UserID string `json:"user_id" gorm:"size:20;index"` // From User.ID
RoleID string `json:"role_id" gorm:"size:20;index"` // From Role.ID
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
RoleName string `json:"role_name" gorm:"<-:false;-:migration;"` // From Role.Name
}
func (a *UserRole) TableName() string {
return config.C.FormatTableName("user_role")
}
// Defining the query parameters for the `UserRole` struct.
type UserRoleQueryParam struct {
util.PaginationParam
InUserIDs []string `form:"-"` // From User.ID
UserID string `form:"-"` // From User.ID
RoleID string `form:"-"` // From Role.ID
}
// Defining the query options for the `UserRole` struct.
type UserRoleQueryOptions struct {
util.QueryOptions
JoinRole bool // Join role table
}
// Defining the query result for the `UserRole` struct.
type UserRoleQueryResult struct {
Data UserRoles
PageResult *util.PaginationResult
}
// Defining the slice of `UserRole` struct.
type UserRoles []*UserRole
func (a UserRoles) ToUserIDMap() map[string]UserRoles {
m := make(map[string]UserRoles)
for _, userRole := range a {
m[userRole.UserID] = append(m[userRole.UserID], userRole)
}
return m
}
func (a UserRoles) ToRoleIDs() []string {
var ids []string
for _, item := range a {
ids = append(ids, item.RoleID)
}
return ids
}
// Defining the data structure for creating a `UserRole` struct.
type UserRoleForm struct {
}
// A validation function for the `UserRoleForm` struct.
func (a *UserRoleForm) Validate() error {
return nil
}
func (a *UserRoleForm) FillTo(userRole *UserRole) error {
return nil
}

View File

@ -0,0 +1,93 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/util"
)
const (
VideoStatusDisabled = "disabled"
VideoStatusEnabled = "enabled"
)
var (
VideoOrderParams = []util.OrderByParam{
{Field: "created_at", Direction: util.DESC},
{Field: "sequence", Direction: util.DESC},
}
VideoOrderNoCreatedAtParams = []util.OrderByParam{
{Field: "sequence", Direction: util.DESC},
}
)
// Article management for RBAC
type Video struct {
ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID
Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner
Subheading string `json:"subheading" gorm:"size:128;index"` // Display name of banner
SmallImg string `json:"smallImg" gorm:"size:1024"`
VideoUrl string `json:"videoUrl" gorm:"size:1024"`
FullImg string `json:"fullImg" gorm:"size:1024"` // Details about banner
Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc)
Link string `json:"link" gorm:"size:1024;"`
PushAt string `json:"pushAt" gorm:"size:50;index"`
Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus
CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time
UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time
}
func (a *Video) TableName() string {
return config.C.FormatTableName("video")
}
// Defining the query parameters for the `Menu` struct.
type VideoQueryParam struct {
util.PaginationParam
LikeTitle string `form:"title"` // Display name of menu
Typer string `form:"type" bind:"required,max=50"`
Status string `form:"status"` // Status of menu (enabled, disabled)
}
// Defining the query options for the `Menu` struct.
type VideoQueryOptions struct {
util.QueryOptions
}
// Defining the query result for the `Menu` struct.
type VideoQueryResult struct {
Data Videos
PageResult *util.PaginationResult
}
// Defining the slice of `Menu` struct.
type Videos []*Video
func (a Videos) Len() int {
return len(a)
}
type VideoForm struct {
Title string `json:"title" binding:"required,max=128"` // Display name of menu
Sequence int `json:"sequence"` // Sequence for sorting (Order by desc)
Link string `json:"link" `
PushAt string `json:"pushAt"`
VideoUrl string `json:"videoUrl"`
SmallImg string `json:"smallImg" `
FullImg string `json:"fullImg" `
Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled)
}
func (a *VideoForm) FillTo(video *Video) error {
video.Title = a.Title
video.PushAt = a.PushAt
video.VideoUrl = a.VideoUrl
video.Sequence = a.Sequence
video.SmallImg = a.SmallImg
video.FullImg = a.FullImg
video.Link = a.Link
video.Status = a.Status
return nil
}

View File

@ -0,0 +1,59 @@
package schema
import (
"time"
"github.com/guxuan/hailin_service/internal/config"
)
type WebSite struct {
ID string `json:"id" gorm:"size:20;primarykey;"`
ReportNum string `json:"reportNum" gorm:"size:20"`
ReportContent string `json:"reportContent" gorm:"size:2048"`
ReportImage string `json:"reportImage" gorm:"size:1024"`
Address string `json:"address" gorm:"size:1024"`
Phone string `json:"phone" gorm:"size:50"`
Email string `json:"email" gorm:"size:50"`
SocialMedia *[]SocialMedia `json:"socialMedia" gorm:"serializer:json"`
Lon float64 `json:"lon" gorm:""`
Lat float64 `json:"lat" gorm:""`
Affirm string `json:"affirm" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"index;"`
UpdatedAt time.Time `json:"updated_at" gorm:"index;"`
}
type SocialMedia struct {
Name string `json:"name" gorm:"size:50"`
Image string `json:"image" gorm:"size:1024"`
Link string `json:"link" gorm:"size:1024"`
}
func (a *WebSite) TableName() string {
return config.C.FormatTableName("web_site")
}
type WebSiteForm struct {
ReportNum string `json:"reportNum"`
ReportContent string `json:"reportContent"`
ReportImage string `json:"reportImage"`
Address string `json:"address"`
Phone string `json:"phone" `
Email string `json:"email" `
SocialMedia *[]SocialMedia `json:"socialMedia" `
Lon float64 `json:"lon" `
Lat float64 `json:"lat" `
Affirm string `json:"affirm"`
}
func (a *WebSiteForm) FillTo(webSite *WebSite) error {
webSite.ReportNum = a.ReportNum
webSite.ReportContent = a.ReportContent
webSite.ReportImage = a.ReportImage
webSite.Address = a.Address
webSite.Phone = a.Phone
webSite.Email = a.Email
webSite.SocialMedia = a.SocialMedia
webSite.Lon = a.Lon
webSite.Lat = a.Lat
webSite.Affirm = a.Affirm
return nil
}

View File

@ -0,0 +1,66 @@
package rbac
import (
"github.com/google/wire"
"github.com/guxuan/hailin_service/internal/mods/rbac/api"
"github.com/guxuan/hailin_service/internal/mods/rbac/biz"
"github.com/guxuan/hailin_service/internal/mods/rbac/dal"
)
// Collection of wire providers
var Set = wire.NewSet(
wire.Struct(new(RBAC), "*"),
wire.Struct(new(Casbinx), "*"),
wire.Struct(new(api.Upload), "*"),
wire.Struct(new(biz.Upload), "*"),
wire.Struct(new(api.Web), "*"),
wire.Struct(new(dal.Article), "*"),
wire.Struct(new(biz.Article), "*"),
wire.Struct(new(api.Article), "*"),
wire.Struct(new(dal.Video), "*"),
wire.Struct(new(biz.Video), "*"),
wire.Struct(new(api.Video), "*"),
wire.Struct(new(dal.Menu), "*"),
wire.Struct(new(biz.Menu), "*"),
wire.Struct(new(api.Menu), "*"),
wire.Struct(new(dal.Banner), "*"),
wire.Struct(new(biz.Banner), "*"),
wire.Struct(new(api.Banner), "*"),
wire.Struct(new(dal.MenuResource), "*"),
wire.Struct(new(dal.Role), "*"),
wire.Struct(new(biz.Role), "*"),
wire.Struct(new(api.Role), "*"),
wire.Struct(new(dal.RoleMenu), "*"),
wire.Struct(new(dal.User), "*"),
wire.Struct(new(biz.User), "*"),
wire.Struct(new(api.User), "*"),
wire.Struct(new(dal.UserRole), "*"),
wire.Struct(new(biz.Login), "*"),
wire.Struct(new(api.Login), "*"),
wire.Struct(new(api.Logger), "*"),
wire.Struct(new(biz.Logger), "*"),
wire.Struct(new(dal.Logger), "*"),
wire.Struct(new(dal.Job), "*"),
wire.Struct(new(dal.JobArea), "*"),
wire.Struct(new(biz.Job), "*"),
wire.Struct(new(api.Job), "*"),
wire.Struct(new(dal.Memorabilia), "*"),
wire.Struct(new(biz.Memorabilia), "*"),
wire.Struct(new(api.Memorabilia), "*"),
wire.Struct(new(dal.Product), "*"),
wire.Struct(new(dal.ProductCategory), "*"),
wire.Struct(new(biz.Product), "*"),
wire.Struct(new(api.Product), "*"),
wire.Struct(new(dal.Team), "*"),
wire.Struct(new(biz.Team), "*"),
wire.Struct(new(api.Team), "*"),
wire.Struct(new(dal.WebSite), "*"),
wire.Struct(new(biz.WebSite), "*"),
wire.Struct(new(api.WebSite), "*"),
)

5436
internal/swagger/docs.go Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
package prom
import (
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/pkg/promx"
"github.com/guxuan/hailin_service/pkg/util"
"github.com/gin-gonic/gin"
)
var (
Ins *promx.PrometheusWrapper
GinMiddleware gin.HandlerFunc
)
func Init() {
logMethod := make(map[string]struct{})
logAPI := make(map[string]struct{})
for _, m := range config.C.Util.Prometheus.LogMethods {
logMethod[m] = struct{}{}
}
for _, a := range config.C.Util.Prometheus.LogApis {
logAPI[a] = struct{}{}
}
Ins = promx.NewPrometheusWrapper(&promx.Config{
Enable: config.C.Util.Prometheus.Enable,
App: config.C.General.AppName,
ListenPort: config.C.Util.Prometheus.Port,
BasicUserName: config.C.Util.Prometheus.BasicUsername,
BasicPassword: config.C.Util.Prometheus.BasicPassword,
LogApi: logAPI,
LogMethod: logMethod,
Objectives: map[float64]float64{0.9: 0.01, 0.95: 0.005, 0.99: 0.001},
DefaultCollect: config.C.Util.Prometheus.DefaultCollect,
})
GinMiddleware = promx.NewAdapterGin(Ins).Middleware(config.C.Util.Prometheus.Enable, util.ReqBodyKey)
}

129
internal/wirex/injector.go Normal file
View File

@ -0,0 +1,129 @@
package wirex
import (
"context"
"time"
"github.com/guxuan/hailin_service/internal/config"
"github.com/guxuan/hailin_service/internal/mods"
"github.com/guxuan/hailin_service/pkg/cachex"
"github.com/guxuan/hailin_service/pkg/gormx"
"github.com/guxuan/hailin_service/pkg/jwtx"
"github.com/golang-jwt/jwt"
"gorm.io/gorm"
)
type Injector struct {
DB *gorm.DB
Cache cachex.Cacher
Auth jwtx.Auther
M *mods.Mods
}
// It creates a new database connection, and returns a function that closes the connection
func InitDB(ctx context.Context) (*gorm.DB, func(), error) {
cfg := config.C.Storage.DB
resolver := make([]gormx.ResolverConfig, len(cfg.Resolver))
for i, v := range cfg.Resolver {
resolver[i] = gormx.ResolverConfig{
DBType: v.DBType,
Sources: v.Sources,
Replicas: v.Replicas,
Tables: v.Tables,
}
}
db, err := gormx.New(gormx.Config{
Debug: cfg.Debug,
PrepareStmt: cfg.PrepareStmt,
DBType: cfg.Type,
DSN: cfg.DSN,
MaxLifetime: cfg.MaxLifetime,
MaxIdleTime: cfg.MaxIdleTime,
MaxOpenConns: cfg.MaxOpenConns,
MaxIdleConns: cfg.MaxIdleConns,
TablePrefix: cfg.TablePrefix,
Resolver: resolver,
})
if err != nil {
return nil, nil, err
}
return db, func() {
sqlDB, err := db.DB()
if err == nil {
_ = sqlDB.Close()
}
}, nil
}
// It returns a cachex.Cacher instance, a function to close the cache, and an error
func InitCacher(ctx context.Context) (cachex.Cacher, func(), error) {
cfg := config.C.Storage.Cache
var cache cachex.Cacher
switch cfg.Type {
case "redis":
cache = cachex.NewRedisCache(cachex.RedisConfig{
Addr: cfg.Redis.Addr,
DB: cfg.Redis.DB,
Username: cfg.Redis.Username,
Password: cfg.Redis.Password,
}, cachex.WithDelimiter(cfg.Delimiter))
case "badger":
cache = cachex.NewBadgerCache(cachex.BadgerConfig{
Path: cfg.Badger.Path,
}, cachex.WithDelimiter(cfg.Delimiter))
default:
cache = cachex.NewMemoryCache(cachex.MemoryConfig{
CleanupInterval: time.Second * time.Duration(cfg.Memory.CleanupInterval),
}, cachex.WithDelimiter(cfg.Delimiter))
}
return cache, func() {
_ = cache.Close(ctx)
}, nil
}
func InitAuth(ctx context.Context) (jwtx.Auther, func(), error) {
cfg := config.C.Middleware.Auth
var opts []jwtx.Option
opts = append(opts, jwtx.SetExpired(cfg.Expired))
opts = append(opts, jwtx.SetSigningKey(cfg.SigningKey, cfg.OldSigningKey))
var method jwt.SigningMethod
switch cfg.SigningMethod {
case "HS256":
method = jwt.SigningMethodHS256
case "HS384":
method = jwt.SigningMethodHS384
default:
method = jwt.SigningMethodHS512
}
opts = append(opts, jwtx.SetSigningMethod(method))
var cache cachex.Cacher
switch cfg.Store.Type {
case "redis":
cache = cachex.NewRedisCache(cachex.RedisConfig{
Addr: cfg.Store.Redis.Addr,
DB: cfg.Store.Redis.DB,
Username: cfg.Store.Redis.Username,
Password: cfg.Store.Redis.Password,
}, cachex.WithDelimiter(cfg.Store.Delimiter))
case "badger":
cache = cachex.NewBadgerCache(cachex.BadgerConfig{
Path: cfg.Store.Badger.Path,
}, cachex.WithDelimiter(cfg.Store.Delimiter))
default:
cache = cachex.NewMemoryCache(cachex.MemoryConfig{
CleanupInterval: time.Second * time.Duration(cfg.Store.Memory.CleanupInterval),
}, cachex.WithDelimiter(cfg.Store.Delimiter))
}
auth := jwtx.New(jwtx.NewStoreWithCache(cache), opts...)
return auth, func() {
_ = auth.Release(ctx)
}, nil
}

Some files were not shown because too many files have changed in this diff Show More