前言
初二时,我萌生了一个想法:自己做一个学习网站。
不是因为现有的学习网站不好用,也不是因为老师布置了什么作业,就是单纯觉得------如果有一个地方,能让我和同学在上面讨论,成为一个独属于学生自己的空间,好像挺酷的。
那时候我只会 C++ 和一点 html,拿过普及二等,但好像丝毫没用。前端基本靠抄,数据库是什么都还没搞明白。但我还是打开 Visual Studio,新建了一个项目,起名叫"寻梦教育科技"。
现在我是初三,网站已经跑起来了,有用户系统、题库、讨论区、私信、通知,还有社区和后台管理。这篇不是教程,就是想记录一下,这一年我是怎么一点点把它做出来的,踩了多少坑,又是怎么爬出来的。
中间的过程大概是这样的:写代码 → 报错 → 查半天 → 问 AI → 改 → 又报错 → 再查 → 终于跑起来了 → 发现下一个功能又要写。
技术栈
打开 VS 就被吓住了,网站各色技术栈看得我头疼。在查阅了许多资料后,我终于选择了 ASP.NET Core MVC(C#) + SQL Server,前端原生 CSS 和 JS,服务器是 Windows Server。听起来挺唬人,其实很多地方都是边学边做,至于为什么,大概是因为 C# 和我熟悉的 C++ 比较类似吧。但上手以后才发现,踩的坑比写的代码还多。
遇到的困难
说实话,网站能跑起来,一半时间在写代码,另一半时间在修 bug。我这里挑几个印象最深的写。
一、数据库迁移用不了,所有表结构变更只能手写
这是一个非常严重的问题,影响了我后续许多开发的项目,但我到现在还不知道为什么。
项目做到一半,我想给 Users 表加个字段,按照教程敲了 dotnet ef migrations add AddAvatar,结果直接报错------表已经存在,迁移记录对不上,EF Core 不知道从哪一步开始。
试了几种办法都不行,最后干脆放弃 Migration,改成在 Program.cs 里、app.Run() 之前,用 ExecuteSqlRawAsync 直接执行 SQL。每次要加字段或建表,就写一段这样的代码:
csharp
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await dbContext.Database.ExecuteSqlRawAsync(
"IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID('Users') AND name = 'Avatar') " +
"BEGIN ALTER TABLE Users ADD Avatar nvarchar(max) NULL; END"
);
}
看起来笨,但很稳:每次启动自动检查,不存在就加,存在就跳过。后来建 Communities、CommunityMembers、CommunityMessages 三张表,也都是这么写的。
这个经历也给了我一个道理:Migration 不是唯一的路,项目小、表结构已经在跑的时候,手写幂等 SQL 反而更省心。
二、前端发了 JSON,后端收到的是 0
社区功能做完,点「加入社区」一直提示「社区不存在」。前端代码是这样的:
javascript
fetch('/Community/JoinCommunity', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ communityId: communityId })
})
后端是这样:
csharp
public async Task<IActionResult> JoinCommunity(int communityId)
查了半天,社区确实存在,但方法里的 communityId 永远是 0。后来才明白:请求头是 application/json 时,ASP.NET Core 默认不会把 JSON body 绑到普通参数上 ,除非显式加 [FromBody]。
改成:
csharp
public async Task<IActionResult> JoinCommunity([FromBody] CommunityIdRequest request)
请求类:
csharp
public class CommunityIdRequest
{
public int CommunityId { get; set; }
}
问题解决。
前端发 JSON,后端就要用 [FromBody] + 请求类接收;发表单,才用普通参数绑定。这个坑我前后踩了三次(加入社区、退出社区、解散社区),每次都是同一个原因。
三、头像用 Base64 存数据库,页面加载变慢
用户头像我图省事,直接转成 Base64 字符串存在 Users.Avatar 字段里,前端这样用:
html
<img src="@user.Avatar" />
功能没问题,但帖子列表一多,每行都要渲染一大段 Base64,页面明显变慢。
最简单的优化是加 loading="lazy",让浏览器只加载可视区域的头像:
html
<img src="@user.Avatar" loading="lazy" style="width:32px;height:32px;border-radius:50%;object-fit:cover;" />
再进一步,可以在上传时就压缩图片,或者干脆把头像存成文件、数据库只存路径。这个我还在慢慢改。
现在我明白了:Base64 存图片省事,但不适合大量渲染。图片这种大块数据,能存文件就别塞数据库。
部署
部署是个大问题,未成年人不被受限开发和部署,但是没办法备案,再加上经费有限,所以只能选香港的廉价服务器。这边大致贴一下我的支出。
| 项目 | 费用 | 说明 |
|---|---|---|
| 域名 | 约 99 元/年 | 阿里云官方价 |
| 香港云服务器 | 约 288 元/年 | 2核4G,10M 带宽,免备案 |
| SSL 证书 | 0 元 | Let's Encrypt 免费证书,Win-ACME 自动续期 |
| 开发工具 | 0 元 | Visual Studio 社区版、SQL Server Express 都免费 |
| AI | 动态调整 | 网站接入了 Deepseek API,价格看实际用量 |
| 合计 | 约 400 元/年 | 主要成本是服务器 |
时间成本:初二下学期开始,断断续续写了大概 4--5 个月,大部分时间花在踩坑和修 bug 上,真正写功能的时间反而不多。
部署的方式是这样的:
Windows Server + IIS,用 dotnet Xunmeng.Talk.dll --urls "http://0.0.0.0:5003" 跑起来,开机自启。
实际运营中的挑战
一:2GB 内存的服务器,装 SQL Server 直接内存溢出
一开始贪便宜,买了一台 2GB 内存的香港服务器。装 SQL Server 的时候,安装程序直接弹 System.OutOfMemoryException,装到一半就崩了。
后来才发现,Windows 系统本身就要吃掉 1.2--1.5GB,SQL Server 安装程序再要几百 MB,2GB 根本不够。最后换了轻量级的 SQL Server LocalDB,又手动限制了它的最大内存,才勉强跑起来。
二:中文路径,复制文件直接报"找不到路径"
后台资源管理做复制功能时,英文文件夹复制得好好的,一复制到中文名文件夹就报:
text
Could not find a part of the path '...Resources你好\xxx.png'
一眼就看出 你好 是 你好 的 HTML 实体编码。前端传过来的路径被 HTML 编码了,后端 Path.Combine 不认识这种格式。
解决办法是在后端拿到路径后先解码:
csharp
targetPath = WebUtility.HtmlDecode(targetPath);
三:域名突然跳转到陌生网站
有一段时间,访问自己的域名会被跳转到一个完全陌生的网址,换设备又能正常打开。
用网络拨测工具一查,发现全国大部分节点解析都正常,只有浙江、河北两个移动节点解析到了另一个 IP。查下来是那两个地区的运营商 DNS 被污染了,不是我的服务器被黑。
后来把域名 DNS 从 Cloudflare 换回阿里云,问题消失。
教训 :网站打不开,先分清是"服务器挂了"还是"DNS 解析错了"。用 ping 域名 和拨测工具一看就知道。
四:Cloudflare 代理导致无限重定向
换用 Cloudflare 之后,根域名 xunmeng.asia 想跳转到 www.xunmeng.asia,结果浏览器直接报:
text
ERR_TOO_MANY_REDIRECTS
原因是 Cloudflare 的页面规则和 IIS 的绑定规则打架了,请求在两边来回弹。
最后放弃 Cloudflare 代理,直接换回阿里云 DNS,在服务器端用一条简单的重定向规则解决。
五:恶意小号批量注册
网站开放注册后,很快出现一批无意义小号,有的用临时邮箱收验证码,注册完就开始大量发帖,占用服务器资源,不亚于一次小规模的 DDOS 攻击。
试过验证码,对人工注册没用。最后的组合方案是:
- 邮箱白名单:只允许 QQ、Gmail、163 等常用邮箱域名注册,临时邮箱直接被挡在门外;
- 邮箱验证码:注册必须收验证码,且验证码 5 分钟过期、用后作废;
- 新用户限制:注册后 24 小时内不能发帖;
- 一键清空:后台可以一键清理举报和通知,方便应急。
这套组合拳下来,垃圾小号基本没了。
教训:防机器人的验证码,防不住人工小号。真正有效的是"提高注册成本 + 限制初期权限"。
现在是什么样
目前网站包含这些模块:
- 导航页 :
www.xunmeng.asia,入口页面,也可以免www访问; - 寻梦英语 :
english.xunmeng.asia,英语题库,支持单选、阅读、完形、写作等 7 种题型,选择题可提交评测,写作题显示范文; - 寻梦数学 :
math.xunmeng.asia,数学题库,支持选择、填空、解答题; - 寻梦论坛 :
talk.xunmeng.asia,讨论区、私信、举报、通知,还接入了 DeepSeek 的@AI回复功能。 - 寻梦应用 :
app.xunmeng.asia,导航页的二级界面,实际好像没啥用 - 寻梦论坛客户端 :
download-talk.xunmeng.asia,用最简单的网页包装的方式弄了 Android 和 Windows 的"伪客户端"
用户量不大,主要是自己和同学在用。但每个功能都是自己一行一行写出来的,这种成就感很难形容。
写到这里,其实网站还很简陋。题库里的题目不算多,偶尔还会冒出几个我没测出来的 bug,用户也主要是自己和身边的同学。但每次打开 talk.xunmeng.asia,看到那个自己一行一行敲出来的导航栏,心里还是会有点不真实------这玩意儿居然真的跑在互联网上。
初二刚起步的时候,我连"域名"和"服务器"有什么区别都搞不清楚。买服务器那天,盯着 2 核 2G 的配置纠结了半天,最后为了省钱选了个最便宜的套餐,结果装 SQL Server 的时候内存直接爆了,报错刷了一屏。后来为了省内存,把数据库换成 LocalDB,又因为权限问题折腾到半夜。再后来,网站被临时邮箱注册的机器人刷了一堆垃圾帖子,我才第一次意识到"做网站"不只是把页面写出来,还得防着别人来搞破坏。
这些坑,当时每一个都让我想砸键盘。但现在回头看,恰恰是这些报错和深夜的搜索,让我真正搞懂了一些课本上不会讲的东西:什么是端口,什么是外键,什么是 DNS 解析,为什么中文路径会变成一堆 你好。这些知识不是看视频看来的,是被 bug 逼出来的。
初三了,时间比以前更紧。网站更新得慢了,有时候一周才改一个小功能。但我没打算停。接下来想做的还有不少:把多小题的阅读题做得更顺手,给论坛加上真正好用的滑动验证,等中考结束,也许能把那个一直没做完的邮箱功能补上。
如果你也是中学生,也有一个"想做个什么东西"的念头,我的建议只有一句:先做出来,哪怕很丑 。不用等学完所有教程,不用等配置完美,先让它在 localhost 上跑起来,然后一个一个解决报错。你会发现,那些看起来高不可攀的东西,拆开来不过是无数个"下一步该干什么"。
网站地址是 xunmeng.asia,欢迎来看看。如果它哪天帮到了你,那就是我写这篇文章最大的意义。
中考加油,代码也加油。
------ 一个还在折腾的初三学生