ever-gauzy 实战指南:开源ERP部署、二次开发与项目管理全解析
2026/9/16 8:49:03 网站建设 项目流程

很多团队选开源管理系统,第一反应是 Odoo,第二反应是 ERPNext,却很少有人认真聊 ever-gauzy。我第一次把它从 GitHub 拉下来部署时,心理预期只是找一个能自托管的项目看板,但跑起来之后,它同时给出的客户管理、报价、发票、员工考勤、薪资核算、招聘流程,让我把原来散落在三个系统里的东西逐渐归到了一起。这篇文章就把我这一年多折腾 ever-gauzy 的经验完整写出来,从选型理由、部署跑通、真实业务演练到二次开发和上线踩坑,一次性讲透。

如果你正在给小团队选管理后台,或者单纯想把项目、客户、财务这几摊事用一个能改源码的系统管起来,这篇文章应该能帮你少走不少弯路。

1. ever-gauzy 是什么:一套 TypeScript 全栈的开源企业管理平台

1.1 基本定位与技术栈

ever-gauzy 是 Ever 团队开源的企业管理平台,产品名通常直接叫 Gauzy。它不是一个类似“收银系统”那样的单点工具,而是把一家中小型公司日常运转需要的几个核心场景打包在一起:销售线索、客户关系、项目管理、任务看板、工时记录、发票账单、费用报销、员工考勤、工资核算,甚至招聘流程都在覆盖范围里。

这套系统的技术栈对前端和后端工程师都比较友好。后端主体是 NestJS,基于 TypeScript 写的,ORM 层使用 TypeORM,数据库默认是 PostgreSQL,缓存和队列相关用 Redis;前端是 Angular,整个仓库以 monorepo 方式组织,一次拉下来就能同时看到 API 代码和界面代码。对我来说最舒服的一点是:前后端同一门语言,改后端接口的时候不需要在脑内切换语言上下文。

它的运行方式也符合“数据自己掌控”这个开源诉求。你可以完全离线部署在自己的服务器上,数据库、文件存储、定时任务都在自己手里,不像某些 SaaS 产品,数据搬进去容易搬出来难。

1.2 主要模块与能力边界

我实际用下来,ever-gauzy 的功能大概可以分成四块,给你做个快速速览:

  • 销售与 CRM:管理客户、联系人、销售线索、商机阶段、报价单,支持把报价单一键转成项目;
  • 项目管理:项目、任务、里程碑、看板视图、团队协作,还有配套的时间跟踪能力;
  • 会计与发票:客户发票、供应商账单、费用记录、税率设置、收款状态跟踪,以及基础的利润和收入报表;
  • 人力与薪酬:员工档案、考勤、请假审批、工资项配置,能把工时数据汇总到工资计算里。

这里要提醒一句:它的会计模块属于“业务型财务”,适合记录发票、费用、收入、应收应付,但如果你需要完整的总账、资产负债表、多级科目这种专业财务能力,还是建议把它跟前端数据同步到专业财务系统,比如 Odoo 的会计模块或独立财务软件,别指望一个开源项目解决所有财务合规问题。

技术层面还有一个特点值得强调:多租户设计。我们讲的“租户”在这里更像“空间”的概念,一套部署可以给不同公司或不同事业部做数据隔离,每个租户下可以有多个组织。这个设计对软件服务商来说非常友好,你可以在一个实例上服务多个客户,而每个客户看到的数据互相隔离。

2. 为什么我在 Odoo、ERPNext、ever-gauzy 之间做了这个选择

2.1 三款主流开源 ERP 平台的对比

选型阶段,我其实认真比较了三套系统:Odoo 社区版、ERPNext,还有 ever-gauzy。它们都能自托管,都能做二次开发,但针对“项目制交付团队”这个场景,差异还挺明显。

对比维度Odoo 社区版ERPNextever-gauzy
技术栈Python / XML / 自研 ORMPython / Frappe 框架TypeScript / NestJS / Angular
模块覆盖非常广,但很多模块在社区版有限制广,ERP 味道重聚焦中小团队的业务闭环
二次开发门槛需要学 Odoo 的模块机制和视图语法需要学 Frappe 的 DocType 和 Jinja 模板熟悉 NestJS + Angular 就能上手
项目/工时/发票链路项目模块偏基础,工时和发票要凑模块项目模块不错,但界面定制不轻松这条链路是原生主推场景
社区活跃度很高很高相对小,但更新频率不低
中文与本地化社区生态完善中文资料不少需要自己补充翻译和本地化

我当时最纠结的其实是 Odoo。社区版功能确实强大,但它的整体设计更偏向标准 ERP 流程,项目管理和工时、发票之间的打通没有 ever-gauzy 那么顺。ERPNext 同样优秀,可对我来说最大的问题是团队核心都是 TypeScript 背景,如果硬上一个 Python 技术栈,后续扩功能的成本会明显高出一截。

2.2 从对比到落地:哪种团队值得选它

没有绝对最好的系统,只有适不适合。我自己的判断标准是这样的:

如果你是做项目制交付的团队,比如软件外包、设计公司、咨询公司,核心诉求是“一个客户从线索到合同,再到项目执行和开票收款,整个过程尽量别换系统”,那 ever-gauzy 的主链路非常贴合。原生就有线索、商机、报价单、项目、任务、工时、发票这一条完整路径,不用自己开发模块去把断点接起来。

但如果你是制造业或者有复杂进销存、生产工单、MRP 需求的团队,我建议直接放弃 ever-gauzy,老老实实看 Odoo 或 ERPNext,它们在物料和生产领域比 ever-gauzy 成熟太多。如果你希望系统装好之后业务人员马上能用,团队里却没有一个能看懂 TypeScript 的开发者,我也会建议你再想想,因为这类开源系统出现小问题时,没人懂代码会非常被动。

总结一句话:ever-gauzy 是个“懂项目管理团队诉求”的选手,适合有开发能力、业务以项目交付为主的中小团队,不适合复杂制造的标准化大厂。

3. 本地开发环境搭建:从克隆仓库到跑通前后端

3.1 准备工作与依赖

搭建本地环境其实不复杂,但有几个前置条件得先确认。我踩过最大的坑是 Node 版本不对,导致依赖编译直接报错,花了一个小时排查才发现是版本问题。建议直接用 Node.js 长期支持版本,我当时用的是 18 LTS,后续在 v20 上也跑过,没有问题。

需要提前装好的基础服务包括:

  • PostgreSQL,版本建议 14 以上,因为新版数据库的 JSON 和索引能力对平台这类复杂实体关系有好处;
  • Redis,它的主要用途是缓存、队列和部分 session 支持;
  • Git,这个不用多说;
  • Yarn,仓库是 monorepo 结构,使用 workspaces,用 Yarn 装依赖最省事。

这些都准备好之后,把代码拉下来:

git clone https://github.com/ever-co/ever-gauzy.git cd ever-gauzy yarn install

依赖安装过程可能比较久,因为整个仓库的包数量很多。如果网络环境不好,可以把 npm registry 切到国内镜像再装,但装完之后记得留意 lock 文件有没有被动过,不要随意提交上去。

3.2 环境变量与数据库初始化

这是整个部署流程里最容易出错的一步。仓库里通常会有环境变量示例文件,比如.env.example.env.compose,你需要复制一份出来,改成自己的配置:

cp .env.example .env

我常用的最小配置大概是这样的,具体变量名以你拉取到的仓库为准,但核心逻辑是相通的:

# 数据库 DB_HOST=127.0.0.1 DB_PORT=5432 DB_NAME=gauzy DB_USER=gauzy DB_PASS=change_me # Redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379 # 服务 API_PORT=3000 # JWT 签名密钥,务必换成随机长字符串 JWT_SECRET=please_really_change_me

配置好之后,需要把数据库结构同步进去。ever-gauzy 的实体和迁移都放在后端代码里,通常可以用迁移脚本执行,类似:

yarn migration:run

如果没有现成脚本,也可以通过 TypeORM 的同步模式先跑起来,但那只适合开发环境用,生产环境千万别开自动同步,否则改实体时可能误动表结构。

3.3 启动 API 和前端

后端和前端要分别启动。后端一般跑在 3000 端口,前端开发服务器由 Angular CLI 管理,默认通常跑在 4200。我在本地一般开两个终端窗口:

# 终端一:API yarn start:api # 终端二:前端 UI yarn start:gauzy

两个服务都起来后,访问前端地址,第一次进入会要求初始化管理员账号。这里有一个非常容易被忽略的安全点:初始管理员密码不要用示例里的默认值,正式使用前一定要改成强密码,因为这类系统一旦暴露到公网,扫描器会在几分钟内尝试默认口令。

首次登录成功,你会看到左侧一堆菜单。别着急,先熟悉角色切换。系统里面至少分管理员和员工两种主角色,管理员能看到全组织的数据,员工角色能看到的菜单和操作权限是受限的,可以通过后台的角色权限配置动态调整。

3.4 开发环境最值得注意的三个细节

第一,时区设置。这个系统涉及工时、考勤、发票日期,如果服务器和本地时区不一致,你会发现报表里的“今天”和你的理解对不上。建议在环境变量里显式配置时区,不要依赖默认值。

第二,Redis 不是可有可无的装饰品。有些队列任务和缓存依赖它,跳过 Redis 虽然系统能启动,但某些异步任务会一直失败,排查起来很费劲。

第三,别用 root 账号跑开发服务。我做演示时不管,但日常开发建议建一个专用系统用户,避免项目自动生成的上传文件权限乱掉。

4. 用真实业务跑一遍全流程:客户、项目、工时、发票

4.1 客户与联系人管理

理论上跑通环境只是开始,真正让我认可这套系统的,是它能把一条业务链路完整走下来。我用一个具体场景说明:假设你做外包,签了一个零售品牌客户,合同金额 15 万,交付周期 3 个月。

第一步不是建项目,而是在销售模块里把客户和联系人建好。客户资料里可以记录行业、规模、地址,联系人可以保存姓名、职位、手机、邮箱。这里有个贴心设计:联系人可以设置“是否公开”,团队内部可见或者仅对特定角色可见。对服务型公司来说,这能避免销售负责人离职后把客户联系方式一并带走的问题,因为数据归公司系统所有。

4.2 从报价到项目与任务拆解

客户建好之后,接下来是创建报价单。报价单里可以添加多个报价条目,比如“需求调研”“UI 设计”“开发实施”“测试交付”,每个条目有自己的数量和单价。税率也能配置,生成报价单后可以发给客户确认。

业务上最关键的转折点在这里:报价单确认后可以直接转化成项目,项目下再按功能模块拆分成多个里程碑,里程碑里再建立任务,任务支持看板视图。我经常把看板按“待办、开发中、测试中、已完成”四个列表组织,每个任务可以指派负责人、设置截止日期、关联到里程碑,还可以从任务里直接记录工时。

这套链路好在哪里?好在你不需要把客户信息在“销售系统”和“项目系统”里复制粘贴一遍,系统里客户、报价、项目是关联的,报表可以一直追溯到源头。

4.3 时间跟踪与发票生成

项目执行过程中,成员在任务上记录耗费的工时。你可以手动在任务里填,也可以用配套的时间跟踪方式记录起止时间。到月底或者里程碑完成时,项目负责人把工时汇总给管理员,管理员确认工时后,就可以在发票模块创建客户发票。

发票可以从项目一键带出工时和费用条目,也可以手动添加服务项。我常用的做法是:每个里程碑完成时开一张对应金额的发票,收到客户打款后在系统里把发票状态改成“已支付”。这样一来,应收账款一目了然,不用再用 Excel 来回统计“到底还有多少钱没回”。

4.4 收入、费用与基础报表

在开票收款的同时,把项目过程中产生的支出也录进系统,比如外包人力成本、服务器费用、差旅报销。系统里有一个基本逻辑:收入减去费用等于利润,这是项目维度的利润核算。它在报表里会按组织、项目、客户等维度做汇总,管理层看经营状况时不用等财务月底给 Excel。

但这个模块的边界还是要说清楚:它更适合“看一个项目赚不赚钱”这种管理层视角,不适合做严格的财务凭证和税务申报。我的做法是,ever-gauzy 承担运营层面的收入费用记录,月底把汇总数据导出,交给财务同事在专业软件里做合规账和报税。这样两边各司其职,既不让业务人员天天去学会计科目,也避免财务合规环节出问题。

5. 二次开发实战:加字段、加接口、加页面

5.1 Monorepo 里如何找代码入口

第一次接触这个仓库的人容易被目录结构吓到,因为站在根目录往下看,包非常多。但别慌,结构上是清晰的:API 相关的代码集中在后端应用目录下,核心后端服务按业务模块分文件夹,比如客户、项目、发票、员工等,每个模块下通常会拆成 entity、dto、service、controller 这几个文件。前端应用放在另一个主要应用目录下,Angular 的页面组件、服务和状态管理也按业务域分文件。

我自己的经验是:想改什么业务,先在模块文件夹里找 controller,看它暴露了哪些接口,再顺着 service 找到数据操作逻辑,最后去前端找对应的 service 文件,一层一层跟踪下去。整个过程就是很标准的 NestJS + Angular 开发模式,没有额外需要学习的神秘框架。

5.2 后端扩展示例:实体、控制器与服务

给你展示一个最典型的二次开发场景:我希望给项目增加一个“是否属于战略客户项目”的标记,并在接口里返回这个字段。在后端,我先新增或扩展实体的字段定义,类似这样:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; @Entity('project_strategy_flag') export class ProjectStrategyFlag { @PrimaryGeneratedColumn('uuid') id: string; @ApiProperty() @Column({ length: 100 }) projectId: string; @ApiProperty() @Column({ default: false }) isStrategic: boolean; }

然后写一个简单的 service,负责转发数据库操作:

import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ProjectStrategyFlag } from './project-strategy-flag.entity'; @Injectable() export class ProjectStrategyFlagService { constructor( @InjectRepository(ProjectStrategyFlag) private readonly repository: Repository<ProjectStrategyFlag>, ) {} async findByProject(projectId: string): Promise<ProjectStrategyFlag[]> { return this.repository.find({ where: { projectId } }); } async save(payload: ProjectStrategyFlag): Promise<ProjectStrategyFlag> { return this.repository.save(payload); } }

最后在 controller 里注册成 API:

@UseGuards(JwtAuthGuard) @Controller('project-strategy-flags') export class ProjectStrategyFlagController { constructor(private readonly service: ProjectStrategyFlagService) {} @Get('project/:projectId') async findByProject(@Param('projectId') projectId: string) { return this.service.findByProject(projectId); } @Post() async create(@Body() body: ProjectStrategyFlag) { return this.service.save(body); } }

这里我只是演示最小闭环,真实项目里你还得考虑数据校验、事务、权限、租户过滤等细节。不要只在本地改完就完事了,务必在 controller 上加上对应的权限装饰器,否则等所有人能访问你的自定义接口时,数据安全就成了大问题。

5.3 前端对接与页面展示

前端的对接也比较常规。先在 Angular 服务里写一个请求方法:

getStrategyFlags(projectId: string): Observable<ProjectStrategyFlag[]> { return this.http.get<ProjectStrategyFlag[]>( `/api/project-strategy-flags/project/${projectId}` ); }

然后在项目详情页或列表页调用这个服务,把返回的字段渲染到表格里。Angular 开发体验整体还是顺畅的,但要注意响应式数据的不可变更新习惯,尤其是表单绑定时,尽量把 API 返回的数据复制一份再修改,避免因为对象引用问题引起页面不刷新。

如果是更复杂的扩展,比如要新增一个独立的一级菜单,那还涉及路由注册和菜单权限配置。这个也不难,但要在前端路由里注册页面,同时去后端的权限配置里给对应角色开放菜单。很多时候你发现菜单没显示,不是因为组件写错了,而是当前登录用户没有这个菜单的权限。

5.4 不要把权限和租户隔离忽略掉

这一点我想单独拎出来说,因为我看过太多二次开发只顾着“功能通了”的例子。ever-gauzy 的多租户机制决定了你在新增数据表时,几乎都要考虑租户隔离:这张表是否应该按租户隔离,还是全局共享?如果不加租户字段,也不在查询里做过滤,那么 A 公司的客户数据出现在 B 公司的界面上,只是时间问题。

正确做法是:所有业务实体都继承或显式包含租户标识字段,查询时从 JWT 里解析出当前用户所属租户,在 service 层强制加过滤条件。后端框架通常会有全局的能力帮你做一部分,但自定义表不会自动具备你想要的隔离逻辑,必须自己补。

另外,如果你扩展了接口,记得给接口加上权限控制。一套系统里,管理员能做的事和普通员工能做的不应该完全相同。宁可在开始时把权限收紧,再慢慢放开,也不要默认全开放。

6. 我实际部署中踩过的坑:内存、权限、国际化与构建

6.1 全栈同时启动导致内存爆掉

第一次我在本地把 API、前端、数据库、Redis 全开起来,16G 内存的笔记本直接卡到鼠标都飘。原因很简单:Angular 开发服务器本身就吃内存,NestJS 进程也不小,再加上数据库和多个 Node 工具链,内存自然紧张。

解决办法分几个层面。开发阶段,不用的模块不要全部启动,按需启动对应服务;前端构建时可以加内存参数限制 Node 堆大小,避免进程无上限增长。真正到了生产环境,用构建好的静态文件托管前端,不用跑 Angular dev server,内存问题会缓解很多。API 侧如果启动了很多 Worker,也要看哪些任务真的需要,不需要的异步 Worker 可以关掉。

6.2 升级后权限数据没迁移干净

这是我最想提醒大家的一个坑。ever-gauzy 迭代速度不慢,偶尔会调整内置角色的默认权限,比如新增某个菜单权限,或者在某个角色模板里增加按钮。正常升级时数据库迁移会帮你加字段,但已经存在的角色数据不一定被同步更新,结果就是升级后,用户角色缺少新菜单的权限,页面看起来像“丢了一部分菜单”。

遇到这个问题,不要去生产数据库手动改几十条记录,正确做法是:升级前把权限相关的表先备份,升级后对比变更日志或发布说明,看有没有权限模板调整,必要时用脚本批量补齐。我在踩过这个坑之后,把备份命令加到了每个发布流程里,再也没有乱过。

权限表相关的典型备份命令大概是:

pg_dump "postgres://gauzy:change_me@localhost:5432/gauzy" \ -t roles -t role_permissions -t permissions \ -F c -f gauzy_permissions_$(date +%F).dump

6.3 界面冒出翻译 key 而不是中文

ever-gauzy 官方界面支持多种语言,中文翻译其实已经覆盖了不少。但二次开发新增的字段、菜单,以及一些比较偏门的模块,界面很容易显示成类似menu.help_center这样的翻译 key,而不是正常的文字。那些多数情况是前端资源配置里缺少对应语言的词条。

解法简单直接:找到前端国际化资源文件,按 key 补上目标语言的翻译。不要试图去改打包后在 node_modules 里的文件,因为下次安装依赖就没了。正确姿势是把自定义翻译放到项目的扩展资源位置,或者提给上游社区。顺便说一句,如果你把系统交给不会英文的同事用,这一项一定要在上线前检查一遍,否则业务人员会觉得系统是坏的,其实只是缺翻译。

6.4 前端构建慢、产物大怎么处理

Angular 项目构建慢是普遍问题,ever-gauzy 的前端模块很多,首次构建等几分钟很正常。优化方案我验证过几个:

  • 构建时开启生产模式,关闭 source map,代码压缩效果非常明显;
  • 把变化不频繁的第三方库分离出来,利用 Angular 的缓存机制;
  • 静态资源放在 CDN 或 Nginx 层做缓存,让客户端不要每次请求都拿全量文件;
  • 在后端和反向代理层同时开启 gzip 或 brotli 压缩,前端包体积可以被压掉 60% 以上。

发布之后记得检查浏览器控制台有没有报资源 404,确认构建产物路径和 Nginx 静态目录匹配。这个坑我踩过好几次,本地正常,线上白屏,最后发现是路径前缀不对。

7. 生产环境部署与团队落地的一些实在建议

7.1 用 Docker Compose 快速搭生产环境

生产环境我不建议手动在裸机上一点点装依赖,太容易漏。用 Docker Compose 做编排是比较稳妥的路子。下面是一个精简的编排示例,给你参考:

services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: gauzy POSTGRES_USER: gauzy POSTGRES_PASSWORD: change_me volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7-alpine restart: unless-stopped api: build: ./apps/api restart: unless-stopped env_file: .env.production depends_on: - postgres - redis ports: - "3000:3000" ui: build: ./apps/gauzy restart: unless-stopped depends_on: - api ports: - "8080:80" volumes: pgdata:

实际使用时要根据你拉取的仓库构建目录和镜像名调整,但思路是不变的:数据库和 Redis 做数据卷持久化,API 和前端通过环境变量传参,服务挂了能自动重启。强烈建议不要在生产环境把数据库密码写在 compose 文件里,用环境变量或密钥管理工具注入。

7.2 网关与 HTTPS、静态资源缓存

前端构建产物放在 Nginx 里做静态托管,API 走反向代理转发到后端端口,这样对外只暴露 80/443。我在 Nginx 里最常用的一段配置大概是:

server { listen 80; server_name your-domain.example; client_max_body_size 20m; location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location / { root /opt/gauzy/www; try_files $uri $uri/ /index.html; } }

上线必须配 HTTPS,这是底线,不是可选项。用 Let's Encrypt 就能免费申请证书,之后在 Nginx 里加上 443 的 server 块并做 80 跳转。文件上传大小也要调一下,默认的 1M 限制对附件场景实在太小,我一般调到 20M 以上,具体看你业务需要传什么文件。

7.3 备份与恢复的底线操作

很多人装好系统第一件事是加员工加数据,却把备份忘在脑后。我的习惯是:数据库备份走定时任务,每天凌晨执行一次,同时保留最近 14 天的备份文件,并且每两周做一次恢复演练。备份不只是把数据库文件复制出来就能安心,你要定期验证“用这份备份能不能恢复出一个可用的系统”。恢复不出来的备份等于没有备份。

典型备份命令前面提到过,生产环境还要把上传文件目录一起备份。如果使用 S3 或对象存储保存文件就更省心,至少数据不会跟单台服务器的磁盘一起消失。

7.4 团队落地时别犯的流程错误

技术层面没问题,团队落地反而容易翻车。我见得太多次“系统功能全,但业务不用”的情况。犯的错基本是同一个:想一次性把所有历史数据、所有历史流程都搬进去,结果导入数据格式对不上,业务人员觉得系统麻烦,最后弃用。

正确的推进方式是分阶段走:

  • 第一个月只启用客户、项目和发票,把这三个模块用熟,同时把新项目的流程全部在系统里走;
  • 第二个月开始启用工时和报表,要求项目成员每周在系统里补工时;
  • 第三个月再启用考勤和招聘流程,给人事部门适应时间。

历史数据不要追求一次全量迁移,把未完成的项目和在途发票放进去就够了,历史归档数据放在系统外反而安全。等团队真正形成使用习惯,再考虑逐步导入更多历史数据也不迟。

最后再分享一点个人体会

ever-gauzy 不是一个装完就万事大吉的产品,它更像一个基本功扎实的半成品平台,需要你根据自己的业务去打磨。我这个过程中最大收获,其实是把“客户—项目—工时—发票”这条链路彻底理顺了,业务部门不用再反复用 Excel 传文件,管理层也能随时看到项目有没有超支、钱有没有收回。

如果你决定入坑,建议从本地环境搭建开始,先在测试环境里完整跑一遍业务全流程,再决定要不要上生产。另外,因为上游社区更新频率不低,建议配置好 upstream 仓库,定期关注新版本的特性和数据库迁移说明,避免大版本跳着升级造成不必要的兼容问题。最后还是要叮嘱一句:任何自托管的系统,安全责任都在你自己身上,默认密码、密钥管理、备份恢复这三件事,宁可多做,不能不做。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询