feat: 将静态内容调整为embed嵌入,减少外部依赖文件 (#325)

* feat: 将静态内容调整为embed嵌入,减少外部依赖文件
This commit is contained in:
二丫讲梵 2024-04-04 16:27:34 +08:00 committed by GitHub
parent 114a7c3dab
commit 103d765735
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 181 additions and 178 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
tmp
logs
public/static/dist

View File

@ -1,4 +1,4 @@
name: build
name: build and push binary to release
on:
release:
@ -16,6 +16,8 @@ jobs:
goos: windows
steps:
- uses: actions/checkout@v3
- run: |
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }} # 一个默认的变量,用来实现往 Release 中添加文件
@ -23,4 +25,4 @@ jobs:
goarch: ${{ matrix.goarch }}
goversion: 1.18 # 可以指定编译使用的 Golang 版本
binary_name: "go-ldap-admin" # 可以指定二进制文件的名称
extra_files: LICENSE config.yml go-ldap-admin-priv.pem go-ldap-admin-pub.pem rbac_model.conf README.md # 需要包含的额外文件
extra_files: LICENSE config.yml README.md # 需要包含的额外文件

View File

@ -11,16 +11,20 @@ jobs:
with:
go-version: 1.18
- uses: actions/checkout@v3
- run: |
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.47.3
version: v1.57.2
args: --timeout=5m --skip-files="public/client/feishu/feishu.go"
build:
name: go-build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
- name: Set up Go
uses: actions/setup-go@v3
with:

1
.gitignore vendored
View File

@ -24,3 +24,4 @@ go-ldap-admin.db
# vendor/
tmp
docs/docker-compose/data
dist

View File

@ -1,39 +1,29 @@
FROM golang:1.18.10-alpine3.16 AS builder
# ENV GOPROXY https://goproxy.io
RUN mkdir /app && apk add --no-cache --virtual .build-deps \
ca-certificates \
gcc \
g++
ADD . /app/
FROM registry.cn-hangzhou.aliyuncs.com/ali_eryajf/golang:1.18.10-alpine3.17 AS builder
WORKDIR /app
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories \
&& apk upgrade && apk add --no-cache --virtual .build-deps \
ca-certificates gcc g++ curl
ADD . .
RUN release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
RUN sed -i 's@localhost:389@openldap:389@g' /app/config.yml \
&& sed -i 's@host: localhost@host: mysql@g' /app/config.yml && go build -o go-ldap-admin .
### build final image
FROM alpine:3.16
FROM registry.cn-hangzhou.aliyuncs.com/ali_eryajf/alpine:3.19
# we set the timezone `Asia/Shanghai` by default, you can be modified
# by `docker build --build-arg="TZ=Other_Timezone ..."`
ARG TZ="Asia/Shanghai"
ENV TZ ${TZ}
RUN mkdir /app
LABEL maintainer eryajf@163.com
WORKDIR /app
COPY --from=builder /app/ .
RUN apk upgrade \
&& apk add bash tzdata sqlite vim \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo ${TZ} > /etc/timezone
COPY --from=builder /app/wait .
COPY --from=builder /app/LICENSE .
COPY --from=builder /app/config.yml .
COPY --from=builder /app/go-ldap-admin .
RUN chmod +x wait go-ldap-admin

View File

@ -8,10 +8,6 @@ system:
port: 8888
# 是否初始化数据(没有初始数据时使用, 已发布正式版改为false)
init-data: true
# rsa公钥文件路径(config.yml相对路径, 也可以填绝对路径)
rsa-public-key: go-ldap-admin-pub.pem
# rsa私钥文件路径(config.yml相对路径, 也可以填绝对路径)
rsa-private-key: go-ldap-admin-priv.pem
logs:
# 日志等级(-1:Debug, 0:Info, 1:Warn, 2:Error, 3:DPanic, 4:Panic, 5:Fatal, -1<=level<=5, 参照zap.level源码)
@ -55,11 +51,6 @@ mysql:
# 字符集(utf8mb4_general_ci速度比utf8mb4_unicode_ci快些)
collation: utf8mb4_general_ci
# casbin配置
casbin:
# 模型配置文件, config.yml相对路径
model-path: 'rbac_model.conf'
# jwt配置
jwt:
# jwt标识

View File

@ -1,6 +1,7 @@
package config
import (
_ "embed"
"fmt"
"os"
@ -15,12 +16,18 @@ import (
// 全局配置变量
var Conf = new(config)
//go:embed go-ldap-admin-priv.pem
var priv []byte
//go:embed go-ldap-admin-pub.pem
var pub []byte
type config struct {
System *SystemConfig `mapstructure:"system" json:"system"`
Logs *LogsConfig `mapstructure:"logs" json:"logs"`
Database *Database `mapstructure:"database" json:"database"`
Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"`
Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"`
// Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"`
Jwt *JwtConfig `mapstructure:"jwt" json:"jwt"`
RateLimit *RateLimitConfig `mapstructure:"rate-limit" json:"rateLimit"`
Ldap *LdapConfig `mapstructure:"ldap" json:"ldap"`
@ -50,8 +57,8 @@ func InitConfig() {
panic(fmt.Errorf("初始化配置文件失败:%s", err))
}
// 读取rsa key
Conf.System.RSAPublicBytes = RSAReadKeyFromFile(Conf.System.RSAPublicKey)
Conf.System.RSAPrivateBytes = RSAReadKeyFromFile(Conf.System.RSAPrivateKey)
Conf.System.RSAPublicBytes = pub
Conf.System.RSAPrivateBytes = priv
})
if err != nil {
@ -62,36 +69,16 @@ func InitConfig() {
panic(fmt.Errorf("初始化配置文件失败:%s", err))
}
// 读取rsa key
Conf.System.RSAPublicBytes = RSAReadKeyFromFile(Conf.System.RSAPublicKey)
Conf.System.RSAPrivateBytes = RSAReadKeyFromFile(Conf.System.RSAPrivateKey)
Conf.System.RSAPublicBytes = pub
Conf.System.RSAPrivateBytes = priv
}
// 从文件中读取RSA key
func RSAReadKeyFromFile(filename string) []byte {
f, err := os.Open(filename)
var b []byte
if err != nil {
return b
}
defer f.Close()
fileInfo, _ := f.Stat()
b = make([]byte, fileInfo.Size())
_, err = f.Read(b)
if err != nil {
return b
}
return b
}
type SystemConfig struct {
Mode string `mapstructure:"mode" json:"mode"`
UrlPathPrefix string `mapstructure:"url-path-prefix" json:"urlPathPrefix"`
Port int `mapstructure:"port" json:"port"`
InitData bool `mapstructure:"init-data" json:"initData"`
RSAPublicKey string `mapstructure:"rsa-public-key" json:"rsaPublicKey"`
RSAPrivateKey string `mapstructure:"rsa-private-key" json:"rsaPrivateKey"`
RSAPublicBytes []byte `mapstructure:"-" json:"-"`
RSAPrivateBytes []byte `mapstructure:"-" json:"-"`
}
@ -123,9 +110,9 @@ type MysqlConfig struct {
Collation string `mapstructure:"collation" json:"collation"`
}
type CasbinConfig struct {
ModelPath string `mapstructure:"model-path" json:"modelPath"`
}
// type CasbinConfig struct {
// ModelPath string `mapstructure:"model-path" json:"modelPath"`
// }
type JwtConfig struct {
Realm string `mapstructure:"realm" json:"realm"`

View File

@ -72,7 +72,6 @@ services:
hostname: go-ldap-admin-server
restart: always
environment:
TZ: Asia/Shanghai
WAIT_HOSTS: mysql:3306, openldap:389
ports:
- 8888:8888
@ -86,19 +85,3 @@ services:
- openldap:go-ldap-admin-openldap # ldap容器的 service_name:container_name
networks:
- go-ldap-admin
go-ldap-admin-ui:
image: registry.cn-hangzhou.aliyuncs.com/ali_eryajf/go-ldap-admin-ui
container_name: go-ldap-admin-ui
hostname: go-ldap-admin-ui
restart: always
environment:
TZ: Asia/Shanghai
ports:
- 8090:80
depends_on:
- go-ldap-admin-server
links:
- go-ldap-admin-server:go-ldap-admin-server
networks:
- go-ldap-admin

View File

@ -68,7 +68,7 @@ func main() {
// 启动定时任务
logic.InitCron()
common.Log.Info(fmt.Sprintf("Server is running at %s:%d/%s", host, port, config.Conf.System.UrlPathPrefix))
common.Log.Info(fmt.Sprintf("Server is running at http://%s:%d", host, port))
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.

View File

@ -0,0 +1,91 @@
package middleware
import (
"embed"
"io/fs"
"net/http"
"os"
"path"
"strings"
"github.com/gin-gonic/gin"
)
const INDEX = "index.html"
type ServeFileSystem interface {
http.FileSystem
Exists(prefix string, path string) bool
}
type localFileSystem struct {
http.FileSystem
root string
indexes bool
}
func LocalFile(root string, indexes bool) *localFileSystem {
return &localFileSystem{
FileSystem: gin.Dir(root, indexes),
root: root,
indexes: indexes,
}
}
func (l *localFileSystem) Exists(prefix string, filepath string) bool {
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
name := path.Join(l.root, p)
stats, err := os.Stat(name)
if err != nil {
return false
}
if stats.IsDir() {
if !l.indexes {
index := path.Join(name, INDEX)
_, err := os.Stat(index)
if err != nil {
return false
}
}
}
return true
}
return false
}
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
return Serve(urlPrefix, LocalFile(root, false))
}
// Static returns a middleware handler that serves static files in the given directory.
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
fileserver := http.FileServer(fs)
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if fs.Exists(urlPrefix, c.Request.URL.Path) {
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
}
type embedFileSystem struct {
http.FileSystem
}
func (e embedFileSystem) Exists(prefix string, path string) bool {
_, err := e.Open(path)
return err == nil
}
func EmbedFolder(fsEmbed embed.FS, targetPath string) ServeFileSystem {
fsys, err := fs.Sub(fsEmbed, targetPath)
if err != nil {
panic(err)
}
return embedFileSystem{
FileSystem: http.FS(fsys),
}
}

View File

@ -3,9 +3,8 @@ package common
import (
"fmt"
"github.com/eryajf/go-ldap-admin/config"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
)
@ -24,12 +23,33 @@ func InitCasbinEnforcer() {
Log.Info("初始化Casbin完成!")
}
var casbinModel = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
`
func mysqlCasbin() (*casbin.Enforcer, error) {
a, err := gormadapter.NewAdapterByDB(DB)
if err != nil {
return nil, err
}
e, err := casbin.NewEnforcer(config.Conf.Casbin.ModelPath, a)
m, err := model.NewModelFromString(casbinModel)
if err != nil {
return nil, err
}
e, err := casbin.NewEnforcer(m, a)
if err != nil {
return nil, err
}

6
public/static/static.go Normal file
View File

@ -0,0 +1,6 @@
package static
import "embed"
//go:embed all:dist
var Static embed.FS

View File

@ -1,14 +0,0 @@
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*")

View File

@ -2,11 +2,13 @@ package routes
import (
"fmt"
"net/http"
"time"
"github.com/eryajf/go-ldap-admin/config"
"github.com/eryajf/go-ldap-admin/middleware"
"github.com/eryajf/go-ldap-admin/public/common"
"github.com/eryajf/go-ldap-admin/public/static"
"github.com/gin-gonic/gin"
)
@ -23,6 +25,16 @@ func InitRoutes() *gin.Engine {
// r := gin.New()
// r.Use(gin.Recovery())
r.Use(middleware.Serve("/", middleware.EmbedFolder(static.Static, "dist")))
r.NoRoute(func(c *gin.Context) {
data, err := static.Static.ReadFile("dist/index.html")
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
})
// 启用限流中间件
// 默认每50毫秒填充一个令牌最多填充200个
fillInterval := time.Duration(config.Conf.RateLimit.FillInterval)

View File

@ -1,2 +0,0 @@
> 因为一些测试场景需要依赖配置的初始化,因此这里单独一个目录放类似的测试

View File

@ -1 +0,0 @@
../config.yml

View File

@ -1 +0,0 @@
../go-ldap-admin-priv.pem

View File

@ -1 +0,0 @@
../go-ldap-admin-pub.pem

View File

@ -1,46 +0,0 @@
package test
import (
"fmt"
"testing"
"github.com/eryajf/go-ldap-admin/config"
"github.com/eryajf/go-ldap-admin/public/common"
"github.com/eryajf/go-ldap-admin/public/tools"
"github.com/eryajf/go-ldap-admin/service/isql"
)
func InitConfig() {
// 加载配置文件到全局配置结构体
config.InitConfig()
// 初始化日志
common.InitLogger()
// 初始化数据库(mysql)
common.InitDB()
// 初始化ldap连接
common.InitLDAP()
// 初始化casbin策略管理器
common.InitCasbinEnforcer()
// 初始化Validator数据校验
common.InitValidate()
}
func TestUserExist(t *testing.T) {
InitConfig()
var u isql.UserService
filter := tools.H{
"id": "111",
}
if u.Exist(filter) {
fmt.Println("用户名已存在")
} else {
fmt.Println("用户名不存在")
}
}

View File

@ -1 +0,0 @@
../rbac_model.conf

View File

@ -1,21 +0,0 @@
package test
import (
"fmt"
"testing"
"github.com/eryajf/go-ldap-admin/config"
"github.com/eryajf/go-ldap-admin/public/tools"
)
func TestUnGenPassword(t *testing.T) {
InitConfig()
pass := "$2a$10$FlzrnJeE3Ad8uokvSAl/gunkRZsdREwlFZZqPcwfkekXOc9oAa9KS"
fmt.Printf("秘钥为:%s\n", config.Conf.System.RSAPrivateBytes)
// 密码通过RSA解密
decodeData, err := tools.RSADecrypt([]byte(pass), config.Conf.System.RSAPrivateBytes)
if err != nil {
fmt.Printf("密码解密失败:%s\n", err)
}
fmt.Printf("密码解密后为:%s\n", string(decodeData))
}