diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..6ab06e4 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,34 @@ +name: Deploy im-manager (Blue-Green) + +on: + workflow_dispatch: + inputs: + image_tag: + description: '镜像tag,留空则使用commit短哈希' + required: false + +jobs: + deploy: + runs-on: deploy-host + steps: + - uses: actions/checkout@v4 + + - name: 确定镜像tag + id: vars + run: | + TAG="${{ github.event.inputs.image_tag }}" + [ -z "$TAG" ] && TAG=$(git rev-parse --short HEAD) + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: 构建镜像(前端+后端一体,自包含多阶段构建) + run: docker build -t im-manager:${{ steps.vars.outputs.tag }} . + + - name: 蓝绿部署 + env: + MYSQL_DSN: ${{ secrets.MYSQL_DSN }} + REDIS_ADDR: ${{ secrets.REDIS_ADDR }} + REDIS_DB: ${{ secrets.REDIS_DB }} + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }} + TOKEN_SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }} + run: | + IMAGE=im-manager:${{ steps.vars.outputs.tag }} /opt/apps/im-manager/deploy.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d124c3f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# ---- Stage 1: build the Vue frontend ---- +FROM node:22-alpine AS web-build +WORKDIR /web +RUN npm install -g pnpm +COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./ +RUN pnpm install --frozen-lockfile +COPY web/ . +RUN pnpm run build + +# ---- Stage 2: build the Go backend, embedding the frontend build ---- +FROM golang:1.24-alpine AS server-build +RUN apk add --no-cache git +WORKDIR /src +COPY server/go.mod server/go.sum ./ +RUN go mod download +COPY server/ . + +# Same layout the project's own Makefile produces: built frontend goes into +# resource/public/admin/ before packing/building the backend. +RUN rm -rf resource/public/admin && mkdir -p resource/public/admin +COPY --from=web-build /web/dist/ ./resource/public/admin/ + +# main.go does `import _ "hotgo/internal/packed"`, a file normally generated by +# `gf build` (see server/hack/config.yaml: packSrc/packDst) and gitignored — +# generate it explicitly with the same gf CLI version pinned in go.mod. +RUN go install github.com/gogf/gf/cmd/gf/v2@v2.10.0 +RUN gf pack resource internal/packed/packed.go + +RUN CGO_ENABLED=0 go build -o /out/hotgo main.go + +# ---- Stage 3: runtime ---- +FROM alpine:3.22 + +RUN apk add --no-cache ca-certificates gettext bash tzdata + +WORKDIR /app + +COPY --from=server-build /src/manifest ./manifest +COPY --from=server-build /src/hack ./hack +COPY --from=server-build /out/hotgo . +COPY server/manifest/docker/entrypoint.sh . + +RUN mkdir -p storage/logs storage/data storage/cache storage/diskqueue \ + && chmod +x entrypoint.sh hotgo + +HEALTHCHECK --interval=1m --timeout=3s --start-period=20s \ + CMD nc -z localhost 8000 || exit 1 + +EXPOSE 8000 + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/server/manifest/config/config.template b/server/manifest/config/config.template new file mode 100644 index 0000000..f35a467 --- /dev/null +++ b/server/manifest/config/config.template @@ -0,0 +1,303 @@ +# hotgo系统配置 +system: + # 应用名称 + appName: "hotgo" + # debug开关,开启后接口出现错误时会向前端输出堆栈信息,可选:false|true,默认为true + debug: true + # gf运行模式,可选:not-set|develop|testing|staging|product。product模式下后台【开发工具】菜单自动隐藏 + mode: "develop" + # IP归属地解析方法,可选:cz88|whois,默认为whois + ipMethod: "whois" + # 是否为演示系统,可选:false|true + isDemo: false + # 是否为集群部署,可选:false|true 默认为false。开启集群必须配置redis,通过redis发布订阅进行集群之间的数据同步 + isCluster: false + # 全局请求日志 + log: + switch: true # 日志开关,可选:false|true,默认为true + queue: true # 是否启用队列,启用时需要配置队列信息,可选:false|true,默认为true + module: [ "admin", "api", "default" ] # 需要记录的模块 + skipCode: [ ] # 不记录的状态码,如: ["0", "-1"] + # 系统日志,用于在后台统计异常日志 + serveLog: + switch: true # 日志开关,可选:false|true,默认为true + queue: true # 是否启用队列,启用时需要配置队列信息,可选:false|true,默认为true + levelFormat: [ "WARN", "ERRO", "FATA", "PANI" ] # 需要记录的等级 + # 国际化 + i18n: + switch: true # 国际化开关,可选:false|true,默认为true + defaultLanguage: "zh-CN" # 默认使用语言,可选:zh-CN|zh-TW|en,默认为zh-CN + # 插件资源目录,用于静态文件和模版文件的路径,默认为resource + addonsResourcePath: "resource" + + +# 统一默认日志配置 +defaultLogger: &defaultLogger + level: "all" + flags: 42 + file: "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log" + stdoutColorDisabled: false # 关闭终端的颜色打印。可选:false|true,默认false + writerColorEnable: false # 日志文件是否带上颜色。可选:false|true,默认false,表示不带颜色 + rotateExpire: "7d" # 日志保留天数 + rotateBackupLimit: 2 # 最大备份数量 + rotateBackupCompress: 2 # 日志文件压缩级别,0-9,9最高 + + +# gf配置,配置参考:https://goframe.org/pages/viewpage.action?pageId=44449486 +server: + address: ":8000" # 本地监听地址。默认":8000",多个地址以","号分隔。例如:"192.168.2.3:8000,10.0.3.10:8001" + openapiPath: "/api.json" # OpenAPI接口文档地址 + swaggerPath: "/swagger" # 内置SwaggerUI展示地址 + serverRoot: "resource/public" # 静态文件服务的目录根路径,配置时自动开启静态文件服务。 + DumpRouterMap: false # 是否在Server启动时打印所有的路由列表。 + logPath: "logs/server" # 服务日志保存路径 + ErrorStack: true # 当Server捕获到异常时是否记录堆栈信息到日志中。默认为true + ErrorLogEnabled: true # 是否记录异常日志信息到日志中。默认为true + errorLogPattern: "error/{Y-m-d}.log" # 异常错误日志文件格式。默认为"error-{Ymd}.log" + accessLogEnabled: true # 是否记录访问日志。默认为false + accessLogPattern: "access/{Y-m-d}.log" # 访问日志文件格式。默认为"access-{Ymd}.log" + maxHeaderBytes: "100KB" # 请求头大小限制,请求头包括客户端提交的Cookie数据,默认设置为100KB + clientMaxBodySize: "200MB" # 客户端提交的Body大小限制,同时也影响文件上传大小,默认设置为200MB + serverAgent: "HG HTTP Server" # 服务端Agent信息。默认为"HG HTTP Server" + # PProf配置 + pprofEnabled: true # 是否开启PProf性能调试特性。默认为false + pprofPattern: "/pprof" # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。 + # 服务日志配置 + logger: + path: "logs/server" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + + +# tcp服务 +tcp: + # 服务器 + server: + address: ":8099" + # 客户端 + client: + # 定时任务 + cron: + group: "cron" # 分组名称 + name: "cron1" # 客户端名称 + address: "127.0.0.1:8099" # 服务器地址 + appId: "1002" # 应用名称 + secretKey: "hotgo" # 密钥 + # 系统授权 + auth: + group: "auth" # 分组名称 + name: "auth1" # 客户端名称 + address: "127.0.0.1:8099" # 服务器地址 + appId: "mengshuai" # 应用名称 + secretKey: "123456" # 密钥 + + +# 日志配置 +logger: + # 全局日志:g.Log() + path: "logs/logger" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + # 定时任务:g.Log("cron") + cron: + path: "logs/cron" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + # 消息队列:g.Log("cron") + queue: + path: "logs/queue" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + # tcp服务器:g.Log("tcpServer") + tcpServer: + path: "logs/tcpServer" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + # tcp客户端:g.Log("tcpClient") + tcpClient: + path: "logs/tcpClient" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + + +# Viewer. 配置参考:https://goframe.org/pages/viewpage.action?pageId=1114335 +viewer: + paths: "resource/template" + defaultFile: "index.html" + delimiters: [ "@{", "}" ] + homeLayout: "home/index.html" + + +# 内容设置 +setting: + title: "HotGo" + keywords: "中后台解决方案,gf框架,vue3" + description: "hotgo 是一个基于 goframe2,vue3,vite2,TypeScript,uniapp 的中后台解决方案,它可以帮助你快速搭建企业级中后台项目,相信不管是从新技术使用还是其他方面,都能帮助到你,持续更新中。" + + +# 路由配置 +router: + # 后台 + admin: + # 前缀 + prefix: "/admin" + # 不需要验证登录的路由地址 + exceptLogin: [ + "/sms/send", # 短信验证码 + "/wechat/authorizeCall", # 微信用户授权回调 + ] + # 不需要验证权限的路由地址 + exceptAuth: [ + "/member/info", # 登录用户信息 + "/role/dynamic", # 获取动态路由 + "/notice/pullMessages", # 拉取我的消息 + "/notice/readAll", # 设置全部已读公告 + "/notice/upRead", # 已读指定公告 + "/dictData/option", # 单个数据字典 + "/dictData/options", # 多个数据字典 + "/provinces/select", # 动态省市区选项 + "/provinces/cityLabel", # 动态解析省市区名称 + "/member/option", # 用户选项 + ] + # 接口 + api: + # 前缀 + prefix: "/api" + # 不需要验证登录的路由地址 + exceptLogin: [ ] + # websocket + websocket: + # 前缀 + prefix: "/socket" + # 不需要验证登录的路由地址 + exceptLogin: [ ] + # 前台页面 + home: + # 前缀 + prefix: "/home" + # 不需要验证登录的路由地址 + exceptLogin: [ ] + + +# 缓存驱动 +cache: + adapter: "redis" # 缓存驱动方式,支持:memory|redis|file,不填默认memory + fileDir: "./storage/cache" # 文件缓存路径,adapter=file时必填 + + +# 登录令牌 +token: + secretKey: "$TOKEN_SECRET_KEY" # 令牌加密秘钥,考虑安全问题生产环境中请修改默认值 + expires: 604800 # 令牌有效期,单位:秒。默认7天 + autoRefresh: true # 是否开启自动刷新过期时间, false|true 默认为true + refreshInterval: 86400 # 刷新间隔,单位:秒。必须小于expires,否则无法触发。默认1天内只允许刷新一次 + maxRefreshTimes: 30 # 最大允许刷新次数,-1不限制。默认30次 + multiLogin: true # 是否允许多端登录, false|true 默认为true + + +# 消息队列 +queue: + switch: true # 队列开关,可选:true|false,默认为true + driver: "redis" # 队列驱动,可选:disk|redis|rocketmq|kafka,默认为disk + groupName: "hotgo" # mq群组名称 + # 磁盘队列 + disk: + path: "./storage/diskqueue" # 数据存放路径 + batchSize: 100 # 每100条消息同步一次,batchSize和batchTime满足其一就会同步一次 + batchTime: 1 # 每1秒消息同步一次 + segmentSize: 10485760 # 每个topic分片数据文件最大字节,默认10M + segmentLimit: 3000 # 每个topic最大分片数据文件数量,超出部分将会丢弃 + # redis,默认使用全局redis运行队列 + redis: + timeout: 0 # 队列超时时间以秒为单位,0表示永不超时。如果队列在设定的超时时间内没有被消费,则会被销毁 + rocketmq: + nameSrvAdders: ["127.0.0.1:9876"] # nameSrvAdder+端口,支持多个 + accessKey: "" # 选填。如果开启了acl控制就必填 + secretKey: "" # 选填。如果开启了acl控制就必填 + brokerAddr: "127.0.0.1:10911" # brokerAddr+端口,选填。用于消费者订阅主题前会检查主题是否存在,不存在会自动创建。你也可以在rocketmq控制台手动创建主题 + retry: 0 # 重试次数 + logLevel: "info" # rocketmq日志输出级别,可选:all|close|debug|info|warn|error|fatal + kafka: + address: "127.0.0.1:9092" # kafka地址+端口 + version: "2.0.0.0" # kafka专属配置,默认2.0.0.0 + randClient: true # 开启随机生成clientID,可以实现启动多实例同时一起消费相同topic,加速消费能力的特性,默认为true + multiConsumer: true # 是否支持创建多个消费者 + + +# Redis. 配置参考:https://goframe.org/pages/viewpage.action?pageId=1114217 +redis: + default: + address: "$REDIS_ADDR" + db: "$REDIS_DB" + pass: "$REDIS_PASSWORD" + idleTimeout: "20" + + +# Database. 配置参考:https://goframe.org/pages/viewpage.action?pageId=1114245 +database: + logger: + path: "logs/database" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger + stdout: true + default: + link: "$MYSQL_DSN" + debug: true + Prefix: "hg_" + + +# 链路追踪 +jaeger: + switch: false # 链路追踪开关,可选:true|false,默认为false + endpoint: "127.0.0.1:6831" # jaeger-agent上报地址 + + +# 生成代码 +hggen: + allowedIPs: [ "127.0.0.1", "*" ] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能 + selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库 + disableTables: [ "hg_sys_gen_codes","hg_admin_role_casbin" ] # 禁用的表,禁用以后将不会在选择表中看到 + delimiters: [ "@{", "}" ] # 模板引擎变量分隔符号 + # 生成应用模型,所有生成模板允许自定义,可以参考default模板进行改造 + application: + # CRUD和关系树列表模板 + crud: + templates: + # 默认的主包模板 + - group: "default" # 分组名称 + isAddon: false # 是否为插件模板 false|true + masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联 + templatePath: "./resource/generate/default/curd" # 模板路径 + apiPath: "./api/admin" # goApi生成路径 + controllerPath: "./internal/controller/admin/sys" # 控制器生成路径 + logicPath: "./internal/logic/sys" # 主要业务生成路径 + inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径 + routerPath: "./internal/router/genrouter" # 生成路由表路径 + sqlPath: "./storage/data/generate" # 生成sql语句路径 + webApiPath: "../web/src/api" # webApi生成路径 + webViewsPath: "../web/src/views" # web页面生成路径 + + # 默认的插件包模板,{$name}会自动替换成实际的插件名称 + - group: "addon" # 分组名称 + isAddon: true # 是否为插件模板 false|true + masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联 + templatePath: "./resource/generate/default/curd" # 模板路径 + apiPath: "./addons/{$name}/api/admin" # goApi生成路径 + controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径 + logicPath: "./addons/{$name}/logic/sys" # 主要业务生成路径 + inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径 + routerPath: "./addons/{$name}/router/genrouter" # 生成路由表路径 + sqlPath: "./storage/data/generate/addons" # 生成sql语句路径 + webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 + webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 + + # 消息队列模板 + queue: + templates: + - group: "default" + templatePath: "./resource/generate/default/queue" + + # 定时任务模板 + cron: + templates: + - group: "default" + templatePath: "./resource/generate/default/cron" + + # 生成插件模块,通过后台创建新插件时使用的模板,允许自定义,可以参考default模板进行改造 + addon: + srcPath: "./resource/generate/default/addon" # 生成模板路径 + webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 + webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 diff --git a/server/manifest/docker/entrypoint.sh b/server/manifest/docker/entrypoint.sh index e8f6e9b..74d61b4 100644 --- a/server/manifest/docker/entrypoint.sh +++ b/server/manifest/docker/entrypoint.sh @@ -1,5 +1,12 @@ -#!/bin/bash +#!/bin/sh +set -e -cd /app && ./hotgo & -echo "hotgo start all server.." -tail -f /dev/null \ No newline at end of file +cd /app + +# Render the real config from the template using only the specific env vars we +# expect — avoids accidentally substituting anything else that might contain a +# literal '$' in the YAML. +envsubst '$MYSQL_DSN $REDIS_ADDR $REDIS_DB $REDIS_PASSWORD $TOKEN_SECRET_KEY' \ + < manifest/config/config.template > manifest/config/config.yaml + +exec ./hotgo diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml index 2fa1171..791bef2 100644 --- a/web/pnpm-workspace.yaml +++ b/web/pnpm-workspace.yaml @@ -1,5 +1,5 @@ allowBuilds: - '@swc/core': set this to true or false - core-js: set this to true or false - esbuild: set this to true or false - vue-demi: set this to true or false + '@swc/core': true + core-js: true + esbuild: true + vue-demi: true