diff --git a/.gitignore b/.gitignore index 4f7e39cc..61bdf2ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .idea .vscode !*.example -dist/ config.yaml *.log paopao-ce* +release +data diff --git a/Dockerfile b/Dockerfile index 3dcf8877..f5c3c36f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,24 @@ -# build app -FROM golang AS build-env - -ADD . /paopao-ce +# build frontend +FROM node:18-alpine as frontend +WORKDIR /web +COPY web/ ./ +RUN echo 'VITE_HOST=""'>.env && yarn && yarn build + +# build backend +FROM golang:1.18-alpine AS backend +RUN apk --no-cache --no-progress add --virtual \ + build-deps \ + build-base \ + git WORKDIR /paopao-ce +COPY . . +COPY --from=frontend /web/dist ./web/dist +ENV GOPROXY=https://goproxy.cn +RUN make build TAGS='embed' -RUN CGO_ENABLED=0 go build . - -# safe image -FROM alpine - +FROM alpine:3.16 ENV TZ=Asia/Shanghai - RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates COPY --from=build-env /paopao-ce/paopao-ce /usr/bin/paopao-ce @@ -22,6 +29,3 @@ COPY config.yaml . EXPOSE 8000 CMD ["paopao-ce"] - -# HEALTHCHECK -HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD ps -ef | grep paopao-ce || exit 1 diff --git a/Makefile b/Makefile index ba019737..5997b45c 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,66 @@ .PHONY: all build run test clean fmt help -BUILD_VERSION := $(shell cat version) + +TARGET = paopao-ce +RELEASE_ROOT = release +RELEASE_FILES = LICENSE README.md config.yaml.sample scripts assets configs +RELEASE_LINUX_AMD64 = $(RELEASE_ROOT)/linux-amd64/$(TARGET) +RELEASE_DARWIN_AMD64 = $(RELEASE_ROOT)/darwin-amd64/$(TARGET) +RELEASE_DARWIN_ARM64 = $(RELEASE_ROOT)/darwin-arm64/$(TARGET) +RELEASE_WINDOWS_AMD64 = $(RELEASE_ROOT)/windows-amd64/$(TARGET) + +BUILD_VERSION := $(shell cat version) BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S') SHA_SHORT := $(shell git rev-parse --short HEAD) + TAGS = "" +LDFLAGS = -X "main.version=${BUILD_VERSION}" -X "main.buildDate=${BUILD_DATE}" -X "main.commitID=${SHA_SHORT}" -w -s + all: fmt build + build: @go mod download @echo Build paopao-ce - bash build.sh paopao-ce + @go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_ROOT)/$(TARGET) + run: - @go run -tags '$(TAGS)' -ldflags "-X 'main.version=${BUILD_VERSION}' -X 'main.buildDate=${BUILD_DATE}' -X 'main.commitID=${SHA_SHORT}'" . + @go run -trimpath -gcflags "all=-N -l" -tags '$(TAGS)' -ldflags '$(LDFLAGS)' . + +.PHONY: release +release: linux-amd64 darwin-amd64 darwin-arm64 windows-x64 + @echo Package paopao-ce + @cp -rf $(RELEASE_FILES) $(RELEASE_LINUX_AMD64) + @cp -rf $(RELEASE_FILES) $(RELEASE_DARWIN_AMD64) + @cp -rf $(RELEASE_FILES) $(RELEASE_DARWIN_ARM64) + @cp -rf $(RELEASE_FILES) $(RELEASE_WINDOWS_AMD64) + @cd $(RELEASE_LINUX_AMD64)/.. && rm -f *.zip && zip -r $(TARGET)-linux_amd64.zip $(TARGET) && cd - + @cd $(RELEASE_DARWIN_AMD64)/.. && rm -f *.zip && zip -r $(TARGET)-darwin_amd64.zip $(TARGET) && cd - + @cd $(RELEASE_DARWIN_ARM64)/.. && rm -f *.zip && zip -r $(TARGET)-darwin_arm64.zip $(TARGET) && cd - + @cd $(RELEASE_WINDOWS_AMD64)/.. && rm -f *.zip && zip -r $(TARGET)-windows_amd64.zip $(TARGET) && cd - + +.PHONY: linux-amd64 +linux-amd64: + @echo Build paopao-ce [linux-amd64] + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_LINUX_AMD64)/$(TARGET) + +.PHONY: darwin-amd64 +darwin-amd64: + @echo Build paopao-ce [darwin-amd64] + @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_DARWIN_AMD64)/$(TARGET) + +.PHONY: darwin-arm64 +darwin-arm64: + @echo Build paopao-ce [darwin-arm64] + @CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_DARWIN_ARM64)/$(TARGET) + +.PHONY: windows-x64 +windows-x64: + @echo Build paopao-ce [windows-x64] + @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_WINDOWS_AMD64)/$(TARGET).exe + clean: @go clean - @find ./dist -type f -exec rm -r {} + - @find ./tmp -type f -exec rm -r {} + + @find ./release -type f -exec rm -r {} + + fmt: @echo Formatting... @go fmt ./global/... @@ -22,14 +69,15 @@ fmt: @go vet -composites=false ./global/... @go vet -composites=false ./internal/... @go vet -composites=false ./pkg/... + test: @go test ./... + help: @echo "make: make" @echo "make run: start api server" - @echo "make build: build executables" - @echo "make build: build executables" + @echo "make build: build executable" + @echo "make release: build release executables" @echo "make run TAGS='embed': start api server and serve embed web frontend" - @echo "make build TAGS='embed': build executables with embed web frontend" -.EXPORT_ALL_VARIABLES: -GO111MODULE = on + @echo "make build TAGS='embed': build executable with embed web frontend" + @echo "make release TAGS='embed': build release executables with embed web frontend" diff --git a/README.md b/README.md index dfdc44bc..edef78a9 100644 --- a/README.md +++ b/README.md @@ -84,20 +84,23 @@ PaoPao主要由以下优秀的开源项目/工具构建 #### 后端 -1. 导入项目根目录下的 `paopao.sql` 文件至MySQL数据库 +1. 导入项目根目录下的 `scripts/paopao.sql` 文件至MySQL数据库 2. 拷贝项目根目录下 `config.yaml.sample` 文件至 `config.yaml`,按照注释完成配置编辑 3. 编译后端 编译api服务: ```sh make build ``` - 编译api服务、内嵌web前端ui; 注意此步骤需要先编译web前端。 + 编译api服务、内嵌web前端ui: ```sh make build TAGS='embed' ``` - 编译后在`dist`目录可以找到对应可执行文件。 + 编译后在`release`目录可以找到对应可执行文件。 + ```sh + release/paopao-ce + ``` -4. 启动后端 +4. 直接运行后端 运行api服务: ```sh make run @@ -106,6 +109,7 @@ PaoPao主要由以下优秀的开源项目/工具构建 ```sh make run TAGS='embed' ``` + 提示: 如果需要内嵌web前端ui,请先构建web前端(建议设置web/.env为VITE_HOST="")。 #### 前端 @@ -148,6 +152,35 @@ PaoPao主要由以下优秀的开源项目/工具构建 桌面端是使用[Rust](https://www.rust-lang.org/) + [tauri](https://github.com/tauri-apps/tauri)编写 的,需要安装tauri的依赖,具体参考[https://tauri.studio/v1/guides/getting-started/prerequisites](https://tauri.studio/v1/guides/getting-started/prerequisites). +### docker-compose 运行 +```sh +%> git clone https://github.com/rocboss/paopao-ce.git +%> docker compose up --build +# visit http://localhost:8008 +``` +默认是使用config.yaml.sample的配置,如果需要自定义配置,请拷贝默认配置文件(比如config.yaml),修改后再同步配置到docker-compose.yaml如下: +``` +# file: docker-compose.yaml +... + backend: + build: + context: . + restart: always + depends_on: + - db + - redis + - zinc + # modify below to reflect your custom configure + volumes: + - ./config.yaml:/app/paopao-ce/config.yaml + ports: + - 127.0.0.1:8008:8008 + networks: + - paopao-network +.... +``` + + ### 其他说明 建议后端服务使用 `supervisor` 守护进程,并通过 `nginx` 反向代理后,提供API给前端服务调用。 diff --git a/build.sh b/build.sh deleted file mode 100644 index f49c00c8..00000000 --- a/build.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2155 -set -e -DIST_PREFIX=${1} -DEBUG_MODE=${2} -TARGET_DIR="dist" -PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 windows/amd64" - -rm -rf ${TARGET_DIR} -mkdir ${TARGET_DIR} - -for pl in ${PLATFORMS}; do - export CGO_ENABLED=0 - export GOOS=$(echo "${pl}" | cut -d'/' -f1) - export GOARCH=$(echo "${pl}" | cut -d'/' -f2) - export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH} - if [ "${GOOS}" == "windows" ]; then - export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH}.exe - fi - - echo "build => ${TARGET}" - if [ "${DEBUG_MODE}" == "debug" ]; then - go build -trimpath -gcflags "all=-N -l" -o "${TARGET}" -tags "${TAGS}" \ - -ldflags "-X 'main.version=${BUILD_VERSION}' \ - -X 'main.buildDate=${BUILD_DATE}' \ - -X 'main.commitID=${SHA_SHORT}'\ - -w -s" - else - go build -trimpath -o "${TARGET}" -tags "${TAGS}" \ - -ldflags "-X 'main.version=${BUILD_VERSION}' \ - -X 'main.buildDate=${BUILD_DATE}' \ - -X 'main.commitID=${SHA_SHORT}'\ - -w -s" - fi -done diff --git a/config.yaml.sample b/config.yaml.sample index 2113be3b..ae78ddd5 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -12,8 +12,8 @@ Server: # 服务设置 ReadTimeout: 60 WriteTimeout: 60 Features: - Default: ["Sms", "Alipay", "Zinc", "MySQL", "Redis", "AliOSS", "LoggerZinc"] - Develop: ["Zinc", "MySQL", "AliOSS", "LoggerFile"] + Default: ["Alipay", "Zinc", "MySQL", "Redis", "AliOSS", "LoggerZinc"] + Develop: ["Sms", "Zinc", "MySQL", "AliOSS", "LoggerFile"] Slim: ["Zinc", "MySQL", "Redis", "AliOSS", "LoggerFile"] Sms: "SmsJuhe" SmsJuhe: @@ -24,11 +24,11 @@ Alipay: AppID: PrivateKey: LoggerFile: # 使用File写日志 - SavePath: storage/logs + SavePath: data/paopao-ce/logs FileName: app FileExt: .log LoggerZinc: # 使用Zinc写日志 - Host: http://127.0.0.1:4080/es/_bulk + Host: http://zinc:4080/es/_bulk Index: paopao-log User: admin Password: admin @@ -36,13 +36,8 @@ JWT: # 鉴权加密 Secret: 18a6413dc4fe394c66345ebe501b2f26 Issuer: paopao-api Expire: 86400 -Search: # 搜索配置 - ZincHost: http://127.0.0.1:4080 - ZincIndex: paopao-data - ZincUser: admin - ZincPassword: admin Zinc: # Zinc搜索配置 - Host: http://127.0.0.1:4080 + Host: http://zinc:4080 Index: paopao-data User: admin Password: admin @@ -53,9 +48,9 @@ AliOSS: # 阿里云OSS存储配置 Bucket: Domain: MySQL: # MySQL数据库 - Username: root # 填写你的数据库账号 - Password: root # 填写你的数据库密码 - Host: 127.0.0.1:3306 + Username: paopao + Password: paopao + Host: db:3306 DBName: paopao TablePrefix: p_ Charset: utf8mb4 @@ -64,6 +59,7 @@ MySQL: # MySQL数据库 MaxIdleConns: 10 MaxOpenConns: 30 Redis: - Host: 127.0.0.1:6379 + Host: redis:6379 Password: - DB: \ No newline at end of file + DB: + \ No newline at end of file diff --git a/init.go b/init.go index c8b3b713..fc549589 100644 --- a/init.go +++ b/init.go @@ -1,7 +1,9 @@ package main import ( + "flag" "log" + "strings" "sync" "time" @@ -14,6 +16,24 @@ import ( "github.com/rocboss/paopao-ce/pkg/zinc" ) +var ( + noDefaultFeatures bool + features suites +) + +type suites []string + +func (s *suites) String() string { + return strings.Join(*s, ",") +} + +func (s *suites) Set(value string) error { + for _, item := range strings.Split(value, ",") { + *s = append(*s, strings.TrimSpace(item)) + } + return nil +} + func init() { const RESTARTS = 5 const SECONDS = 10 @@ -78,6 +98,11 @@ func setupSetting() error { } global.Features = setting.FeaturesFrom("Features") + if len(features) > 0 { + if err = global.Features.Use(features, noDefaultFeatures); err != nil { + return err + } + } objects := map[string]interface{}{ "App": &global.AppSetting, @@ -103,6 +128,12 @@ func setupSetting() error { return nil } +func flagParse() { + flag.BoolVar(&noDefaultFeatures, "no-default-features", false, "whether use default features") + flag.Var(&features, "features", "use special features") + flag.Parse() +} + func setupLogger() error { logger, err := logger.New() if err != nil { diff --git a/pkg/setting/seting_test.go b/pkg/setting/seting_test.go index a834f841..c72b2a59 100644 --- a/pkg/setting/seting_test.go +++ b/pkg/setting/seting_test.go @@ -33,6 +33,79 @@ func TestUseDefault(t *testing.T) { "Sms": true, "Sms = SmsJuhe": true, "SmsJuhe": false, + "default": true, + } { + if ok := features.CfgIf(exp); res != ok { + t.Errorf("CfgIf(%s) want %t got %t", exp, res, ok) + } + } +} + +func TestUse(t *testing.T) { + suites := map[string][]string{ + "default": {"Sms", "Alipay", "Zinc", "MySQL", "Redis", "AliOSS", "LogZinc"}, + "develop": {"Zinc", "MySQL", "AliOSS", "LogFile"}, + "slim": {"Zinc", "MySQL", "Redis", "AliOSS", "LogFile"}, + } + kv := map[string]string{ + "sms": "SmsJuhe", + } + features := newFeatures(suites, kv) + + features.Use([]string{"develop"}, true) + for _, data := range []struct { + key string + expect string + exist bool + }{ + {"Sms", "", false}, + {"Alipay", "", false}, + {"Zinc", "", true}, + {"Redis", "", false}, + {"Database", "", false}, + } { + if v, ok := features.Cfg(data.key); ok != data.exist || v != data.expect { + t.Errorf("key: %s expect: %s exist: %t got v: %s ok: %t", data.key, data.expect, data.exist, v, ok) + } + } + for exp, res := range map[string]bool{ + "Sms": false, + "Sms = SmsJuhe": false, + "SmsJuhe": false, + "default": false, + "develop": true, + } { + if ok := features.CfgIf(exp); res != ok { + t.Errorf("CfgIf(%s) want %t got %t", exp, res, ok) + } + } + + features.UseDefault() + features.Use([]string{"slim", "", "demo"}, false) + for _, data := range []struct { + key string + expect string + exist bool + }{ + {"Sms", "SmsJuhe", true}, + {"Alipay", "", true}, + {"Zinc", "", true}, + {"Redis", "", true}, + {"Database", "", false}, + {"demo", "", true}, + } { + if v, ok := features.Cfg(data.key); ok != data.exist || v != data.expect { + t.Errorf("key: %s expect: %s exist: %t got v: %s ok: %t", data.key, data.expect, data.exist, v, ok) + } + } + for exp, res := range map[string]bool{ + "Sms": true, + "Sms = SmsJuhe": true, + "SmsJuhe": false, + "default": true, + "develop": false, + "slim": true, + "demo": true, } { if ok := features.CfgIf(exp); res != ok { t.Errorf("CfgIf(%s) want %t got %t", exp, res, ok) diff --git a/pkg/setting/settting.go b/pkg/setting/settting.go index d8f8d965..693c400b 100644 --- a/pkg/setting/settting.go +++ b/pkg/setting/settting.go @@ -12,11 +12,6 @@ type Setting struct { vp *viper.Viper } -type LogType string - -const LogFileType LogType = "file" -const LogZincType LogType = "zinc" - type LoggerFileSettingS struct { SavePath string FileName string @@ -30,17 +25,6 @@ type LoggerZincSettingS struct { Password string } -type LoggerSettingS struct { - LogType LogType - LogFileSavePath string - LogFileName string - LogFileExt string - LogZincHost string - LogZincIndex string - LogZincUser string - LogZincPassword string -} - type ServerSettingS struct { RunMode string HttpIp string @@ -84,20 +68,6 @@ type ZincSettingS struct { Password string } -type DatabaseSettingS struct { - DBType string - UserName string - Password string - Host string - DBName string - TablePrefix string - Charset string - ParseTime bool - LogLevel logger.LogLevel - MaxIdleConns int - MaxOpenConns int -} - type MySQLSettingS struct { UserName string Password string @@ -192,25 +162,28 @@ func (s *Setting) FeaturesFrom(k string) *FeaturesSettingS { func newFeatures(suites map[string][]string, kv map[string]string) *FeaturesSettingS { features := &FeaturesSettingS{ - suites: suites, - kv: kv, + suites: suites, + kv: kv, + features: make(map[string]string), } features.UseDefault() return features } func (f *FeaturesSettingS) UseDefault() { - defaultSuite := f.suites["default"] - f.Use(defaultSuite, true) + f.Use([]string{"default"}, true) } func (f *FeaturesSettingS) Use(suite []string, noDefault bool) error { - if noDefault { + if noDefault && len(f.features) != 0 { f.features = make(map[string]string) } features := f.flatFeatures(suite) for _, feature := range features { feature = strings.ToLower(feature) + if len(feature) == 0 { + continue + } f.features[feature] = f.kv[feature] } return nil diff --git a/scripts/README b/scripts/README new file mode 100644 index 00000000..fddd5ac9 --- /dev/null +++ b/scripts/README @@ -0,0 +1 @@ +All files in subdirectories are templates, do modifications based on your environment first. \ No newline at end of file diff --git a/scripts/launchd/info.paopao.web.plist b/scripts/launchd/info.paopao.web.plist new file mode 100644 index 00000000..d4a2fc87 --- /dev/null +++ b/scripts/launchd/info.paopao.web.plist @@ -0,0 +1,32 @@ + + + + + Label + info.paopao.web + + + UserName + paopao + GroupName + paopao + ProgramArguments + + + + /Users/paopao/app/paopao-ce + + RunAtLoad + + KeepAlive + + + + WorkingDirectory + /Users/paopao/app/ + StandardOutPath + /Users/paopao/app/log/stdout.log + StandardErrorPath + /Users/paopao/app/log/stderr.log + + diff --git a/paopao.sql b/scripts/paopao.sql similarity index 100% rename from paopao.sql rename to scripts/paopao.sql diff --git a/scripts/systemd/paopao.service b/scripts/systemd/paopao.service new file mode 100644 index 00000000..00dff061 --- /dev/null +++ b/scripts/systemd/paopao.service @@ -0,0 +1,17 @@ +[Unit] +Description=paopao-ce +After=syslog.target +After=network.target +After=mariadb.service mysqld.service redis.service + +[Service] +Type=simple +User=paopao +Group=paopao +WorkingDirectory=/home/paopao/app +ExecStart=/home/paopao/app/paopao-ce +Restart=always +Environment=USER=paopao HOME=/home/paopao + +[Install] +WantedBy=multi-user.target diff --git a/storage/logs/.gitkeep b/storage/logs/.gitkeep deleted file mode 100644 index e69de29b..00000000