2025年5月,我终于把用 Django 搭了 5 年的网站(经常优化+功能),重构成了 Go 项目,仅仅用了 10 天。这不是因为我有多厉害,而是因为 Gemini 和 ChatGPT 真的太强了,在重建过程中遇到很多奇怪问题,不一一列举了,只写重要的。


为什么要从 Django 换到 Go?

我的 Django 网站运行在一台 2 核 2G 8M 带宽的服务器上,越来越卡。有朋友打开网页要花二三十秒,有时候 CDN 在国内也加载不出来,模板继承一改就报错,页面功能多了之后越来越难维护,写文章有时候会500。

我曾经偶然发现:用render  后端Go 的网站,在一台 512M 内存、0.1 核 CPU 的服务器上都能跑得很流畅,内存只用了 几M 出头,比 Django 省太多了,只要用go可以不花钱了!


重构过程的崩溃与惊喜

为了保证迁移平滑进行,我在 Go 项目中保持了与原 Django 项目完全一致的数据库结构,并且前端模板和样式也做了最小化改动,确保网站外观基本保持不变,后端全部改。这样用户和后台管理都无需适应新的界面,体验无缝衔接。

代码结构好乱,强迫症爆炸

一开始看 Go 的项目结构,满屏的 struct 定义,逻辑都写在不同地方,看着就很焦虑。很多结构体一眼看上去都差不多,我总担心写重复了,而且 Go 没有缩进强制检查,格式一乱,心情也乱。

指针 & 报错满天飞

虽然我学 C++ 很多年,但指针这个东西我还是没搞太明白。Go 的 nil 报错经常让我措手不及。编译器老是提示“invalid memory address or nil pointer dereference”,让我一度怀疑自己是不是该干点别的。

但每次我想放弃时,都是 Gemini 和 ChatGPT 把我救回来。只要把报错一贴、逻辑一说,它们就能秒给解决方案,还能帮我一步一步调试。


评论功能最难搞

要说最折腾的功能,非评论系统莫属。

Django 里我用的是 GenericForeignKey + content_type 来实现“文章、友链都能评论”的逻辑。但 Go 里没有这个玩意,我只好自己写一个字段 comment_type,自己处理评论的来源判断逻辑。

后台管理系统也没现成的 Django Admin,我就写了一个简陋的管理页面,功能不多,但能用。而且 为了安全起见,我加了每页管理员权限判断,虽然注册功能暂时还没写


一些关键技术改进

  • 模板改为 API 返回 JSON,再由前端渲染,避免 Django 模板继承时一改就崩的问题。
  • 评论接口统一处理文章与友链评论,通过类型字段识别来源。
  • 内存使用:Go ~20M(以后可以买更低配服务器了😁)
  [root@server blog]# ps -p 3864229 -o pid,comm,%cpu,%mem
      PID COMMAND         %CPU %MEM
  3864229 main             0.0  1.1

  [root@server blog]# pmap -x 3864229 | grep total
  total kB         1543276   20828    9628
  • 性能测试https://pagespeed.web.dev/(使用 Go 后)
  • FCP(首次内容绘制):0.8 秒
  • LCP(最大内容绘制):1.3 秒
  • TBT(总阻塞时间):170 毫秒
  • CLS(布局偏移):0.005

总分:性能 83,SEO 83,无障碍 77,最佳实践 96


遇到的一些坑

  • 菜单按钮点击无效? 原来是 CDN 加载失败了,国内访问 popper.js 卡得不行,建议尽量自托管或者用国内 CDN。
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
  • GitLab 活动图表数据的插入坑点

模板中原本用 <script id="gitlab-activity-chart-data"> 包含数据,结果浏览器无法读取 JSON,换成 <span style="display:none;"> 就好了。

  <span style="display: none;" id="gitlab-activity-chart-data" type="application/json">
    {{.GitLabChartDataJSON}}
  </span>

 

密码加密方式保持和 Django 一致

为了实现“用户不需要重新注册”,我保留了原有 Django 用户数据库,同时在 Go 端实现了与 Django 相同的密码校验逻辑,默认密码使用 PBKDF2(带 SHA256)进行加密。

 

评论 content_type_id和Django 一致

为了实现“评论显示出来”,我修改评论的数据库(改代码发现没作用,django 的content_type 不清楚怎么实现的,只有这样了🤣)。

UPDATE comments_comment
SET content_type_id = 2,
    object_id       = 0
WHERE content_type_id = 25
  AND object_id       = 1;
  

以后新增评论接口,需要go语言改 增加contentTypeMapNameToID 和对应模块结构体参数定义

comments\comments.go
// --- ContentType 名称与ID转换 (你需要根据你的系统实现或配置这个) ---
var contentTypeMapNameToID = map[string]int64{
	"article":         1, // 示例: 假设 blog.Article 在 django_content_type 表中ID为1
	"friendlink_page": 2, // 示例: 假设某个代表友链页的模型的ID为2
	// ... 定义你所有可评论对象的类型名称到ID的映射 ...
}

friendlinks\handlers.go

var formOnError *comments.CommentFormData
	commentSectionHTML, errRenderComment := comments.RenderCommentSectionForPage(r, commentCfg, formOnError)
	if errRenderComment != nil {
		log.Printf("FriendLinkController.PageHandler: Error rendering comment section: %v", errRenderComment)
		// 提供一个用户友好的回退HTML
		commentSectionHTML = template.HTML("<div class='alert alert-warning' role='alert'>评论区暂时无法加载,请稍后再试。</div>")
	}

	pageData := FriendLinksPageData{
		
		CommentSectionHTML:       commentSectionHTML,
		CommentSubmitSuccess:     r.URL.Query().Get("comment_success"),     // 从URL query获取
		CommentSubmitErrorGlobal: r.URL.Query().Get("comment_form_error"),    // 从URL query获取
	}

 


写在最后:感谢 AI

我不是专业的后端,也不是专业的前端。其实很多功能实现过程都是在 ChatGPT 和 Gemini 的“陪跑”下完成的。

我问的问题千奇百怪,比如:

  • “Go 怎么判断字符串是不是空?”
  • “我的 JSON 怎么解析失败了?”
  • “评论接口设计有没有更优雅的方案?”
  • “我的评论下的回复怎么没显示?”

而它们永远不烦、不累、不会嘲笑我,能给我示例代码、解释思路、还会提醒我注意安全问题。


接下来要做的事

  • 补上用户注册和找回密码功能
  • 完善评论管理 UI 和审核机制
  • 提升前端加载速度,CDN 国内可用
  • 增加缓存策略,避免 API 重复请求
  • 逐步引入前端框架(考虑 HTMX / Alpine.js)
  • 看看服务器稳定性会不会挂

无需 Django 的 Gunicorn:使用 Air 热重载运行

在 Go 项目中,不再依赖 Django 的 Gunicorn 或其他 WSGI 服务器。可以直接使用 Air 实现开发时的热重载,并在生产环境用如下命令后台运行(有更新时air短暂停服务器停几秒重启,supervisor不会挂但比较占内存,这五年间使用supervisor遇到各种问题所有没使用):

nohup air > air.log 2>&1 </dev/null & disown
  • air:支持代码改动后自动重编译并重启服务,极大提升开发效率。
  • nohup + disown:让进程脱离终端,即使关闭 SSH 会话也能继续运行。
  • 日志输出到 air.log,方便排查问题。

这样就省去了额外的进程管理工具,启动简单、可热重载,适合很多中小型 Go 服务。

 


最后给一句建议

如果你正觉得 Django 跑不动了,不妨试试 Go。 如果你觉得 Go 太难了,不妨问问 ChatGPT 或 Gemini。

AI 真的是开发者最强的外挂工具。 感谢它们,帮我完成了曾经觉得不可能完成的工作。