yearning
11 小时以前 3f96c9c6502243b06efd4bb34a189e72e3a5557c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
<!-- Generated by Trae Work -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>考试阅卷系统 - 华为云部署新手教程</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    background: #f5f7fa;
    color: #333;
    line-height: 1.8;
  }
  .container {
    max-width: 960px;
    margin: 0 auto;
    padding: 40px 20px;
  }
  h1 {
    font-size: 32px;
    color: #1a1a2e;
    margin-bottom: 10px;
    text-align: center;
  }
  .subtitle {
    text-align: center;
    color: #666;
    margin-bottom: 40px;
    font-size: 16px;
  }
  .toc {
    background: #fff;
    border-radius: 12px;
    padding: 24px 32px;
    margin-bottom: 32px;
    box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  }
  .toc h2 {
    font-size: 18px;
    margin-bottom: 12px;
    color: #1a1a2e;
  }
  .toc ol {
    padding-left: 24px;
  }
  .toc li {
    margin: 6px 0;
  }
  .toc a {
    color: #0052d9;
    text-decoration: none;
  }
  .toc a:hover {
    text-decoration: underline;
  }
  .section {
    background: #fff;
    border-radius: 12px;
    padding: 32px;
    margin-bottom: 24px;
    box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  }
  .section h2 {
    font-size: 24px;
    color: #1a1a2e;
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 2px solid #e8eaed;
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .section h2 .step-num {
    background: linear-gradient(135deg, #0052d9, #36cffb);
    color: #fff;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    font-weight: bold;
  }
  .section h3 {
    font-size: 18px;
    color: #1a1a2e;
    margin: 20px 0 10px;
  }
  .section p {
    margin: 10px 0;
    color: #444;
  }
  .section ul, .section ol {
    padding-left: 24px;
    margin: 10px 0;
  }
  .section li {
    margin: 6px 0;
    color: #444;
  }
  .code-block {
    background: #1e1e2e;
    color: #cdd6f4;
    padding: 16px 20px;
    border-radius: 8px;
    margin: 12px 0;
    font-family: "Fira Code", "Consolas", monospace;
    font-size: 14px;
    overflow-x: auto;
    line-height: 1.6;
  }
  .code-block .comment { color: #6c7086; }
  .code-block .cmd { color: #a6e3a1; }
  .code-block .var { color: #f9e2af; }
  .tip {
    background: #ecf5ff;
    border-left: 4px solid #409eff;
    padding: 12px 16px;
    border-radius: 4px;
    margin: 12px 0;
    color: #333;
  }
  .tip::before {
    content: "💡 提示:";
    font-weight: bold;
    color: #409eff;
  }
  .warning {
    background: #fdf6ec;
    border-left: 4px solid #e6a23c;
    padding: 12px 16px;
    border-radius: 4px;
    margin: 12px 0;
    color: #333;
  }
  .warning::before {
    content: "⚠️ 注意:";
    font-weight: bold;
    color: #e6a23c;
  }
  .success {
    background: #f0f9eb;
    border-left: 4px solid #67c23a;
    padding: 12px 16px;
    border-radius: 4px;
    margin: 12px 0;
    color: #333;
  }
  .success::before {
    content: "✅ 成功:";
    font-weight: bold;
    color: #67c23a;
  }
  .table-wrapper {
    overflow-x: auto;
    margin: 16px 0;
  }
  table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
  }
  th, td {
    border: 1px solid #e8eaed;
    padding: 10px 14px;
    text-align: left;
  }
  th {
    background: #f5f7fa;
    font-weight: 600;
    color: #1a1a2e;
  }
  tr:nth-child(even) {
    background: #fafbfc;
  }
  .highlight {
    background: #fffbe6;
    padding: 2px 6px;
    border-radius: 3px;
    font-family: monospace;
    font-size: 13px;
  }
  .architecture {
    background: #f8f9fc;
    border-radius: 8px;
    padding: 24px;
    margin: 16px 0;
    text-align: center;
  }
  .arch-box {
    display: inline-block;
    background: #fff;
    border: 2px solid #0052d9;
    color: #0052d9;
    padding: 12px 24px;
    border-radius: 8px;
    margin: 8px;
    font-weight: 600;
  }
  .arch-arrow {
    display: inline-block;
    color: #999;
    font-size: 24px;
    margin: 0 8px;
    vertical-align: middle;
  }
  .faq-item {
    border: 1px solid #e8eaed;
    border-radius: 8px;
    margin: 10px 0;
    overflow: hidden;
  }
  .faq-q {
    background: #f5f7fa;
    padding: 12px 16px;
    font-weight: 600;
    color: #1a1a2e;
  }
  .faq-a {
    padding: 12px 16px;
    color: #555;
  }
  .footer {
    text-align: center;
    color: #999;
    padding: 20px;
    font-size: 14px;
  }
</style>
</head>
<body>
<div class="container">
  <h1>🚀 考试阅卷系统 - 华为云部署新手教程</h1>
  <p class="subtitle">从购买服务器到上线运行,一步步带你完成部署</p>
 
  <div class="toc">
    <h2>📑 目录</h2>
    <ol>
      <li><a href="#overview">项目概览与部署方案</a></li>
      <li><a href="#step1">第一步:购买华为云 ECS 服务器</a></li>
      <li><a href="#step2">第二步:配置安全组(开放端口)</a></li>
      <li><a href="#step3">第三步:远程登录服务器</a></li>
      <li><a href="#step4">第四步:安装运行环境</a></li>
      <li><a href="#step5">第五步:上传项目代码</a></li>
      <li><a href="#step6">第六步:安装项目依赖</a></li>
      <li><a href="#step7">第七步:初始化数据库与管理员</a></li>
      <li><a href="#step8">第八步:启动服务并验证</a></li>
      <li><a href="#step9">第九步:配置开机自启(systemd)</a></li>
      <li><a href="#step10">第十步:绑定域名(可选)</a></li>
      <li><a href="#faq">常见问题 FAQ</a></li>
    </ol>
  </div>
 
  <!-- 0. 项目概览 -->
  <div class="section" id="overview">
    <h2><span class="step-num">📌</span> 项目概览与部署方案</h2>
    
    <h3>项目技术栈</h3>
    <div class="table-wrapper">
      <table>
        <tr><th>层级</th><th>技术</th><th>端口</th></tr>
        <tr><td>前端</td><td>React + TypeScript + Vite + Ant Design</td><td>5174(生产)</td></tr>
        <tr><td>后端</td><td>Python FastAPI + SQLAlchemy + Uvicorn</td><td>8010</td></tr>
        <tr><td>数据库</td><td>SQLite(文件型,无需单独部署)</td><td>-</td></tr>
      </table>
    </div>
 
    <h3>部署架构</h3>
    <div class="architecture">
      <div class="arch-box">用户浏览器</div>
      <div class="arch-arrow">→</div>
      <div class="arch-box">ECS 云服务器</div>
      <div style="margin-top:12px;">
        <div class="arch-box" style="border-color:#67c23a;color:#67c23a;">前端 :5174</div>
        <div class="arch-arrow">→</div>
        <div class="arch-box" style="border-color:#e6a23c;color:#e6a23a;">后端 :8010</div>
        <div class="arch-arrow">→</div>
        <div class="arch-box" style="border-color:#909399;color:#909399;">SQLite 文件</div>
      </div>
    </div>
 
    <h3>为什么选 ECS?</h3>
    <ul>
      <li><strong>最简单</strong>:一台服务器搞定全部,适合新手</li>
      <li><strong>成本低</strong>:SQLite 不需要额外买数据库</li>
      <li><strong>灵活</strong>:后续可以随时升级配置</li>
    </ul>
 
    <div class="tip">
      本教程选择 <strong>华为云弹性云服务器 ECS + Ubuntu 22.04</strong> 操作系统,这是最通用、文档最多的组合。
    </div>
  </div>
 
  <!-- 第一步:购买 ECS -->
  <div class="section" id="step1">
    <h2><span class="step-num">1</span> 购买华为云 ECS 服务器</h2>
 
    <h3>1.1 注册华为云账号</h3>
    <p>访问 <a href="https://www.huaweicloud.com/" target="_blank">华为云官网</a>,点击右上角「注册」,按照提示完成注册和实名认证。</p>
 
    <h3>1.2 购买 ECS</h3>
    <ol>
      <li>登录后,在顶部搜索框输入「弹性云服务器 ECS」,点击进入</li>
      <li>点击「购买弹性云服务器」</li>
      <li>按照以下配置选择:</li>
    </ol>
 
    <div class="table-wrapper">
      <table>
        <tr><th>配置项</th><th>推荐选择</th><th>说明</th></tr>
        <tr><td>计费模式</td><td>按需计费 / 包年包月</td><td>新手先用按需计费试试,稳定后转包月更便宜</td></tr>
        <tr><td>区域</td><td>选离你最近的(如:北京四、上海一)</td><td>影响访问速度</td></tr>
        <tr><td>CPU 架构</td><td>x86 计算</td><td>兼容性最好</td></tr>
        <tr><td>规格</td><td>通用计算型 | s6.large.2(2核4G)</td><td>💡 最低 2核2G 也能跑,但推荐 2核4G 更流畅</td></tr>
        <tr><td>镜像</td><td>公共镜像 → Ubuntu → Ubuntu 22.04 64bit</td><td>本教程基于此系统</td></tr>
        <tr><td>系统盘</td><td>通用型 SSD,40GB</td><td>足够用了</td></tr>
        <tr><td>带宽</td><td>按流量计费,5Mbit/s</td><td>小流量场景省钱</td></tr>
      </table>
    </div>
 
    <h3>1.3 设置登录凭证</h3>
    <p>在「高级配置」中设置登录方式:</p>
    <ul>
      <li><strong>密码方式(推荐新手)</strong>:设置 root 用户密码,请牢记!</li>
      <li>密钥对方式:更安全,但需要生成密钥对</li>
    </ul>
 
    <div class="warning">
      请务必记住你的服务器 <strong>公网 IP 地址</strong> 和 <strong>root 密码</strong>,后面会反复用到。
    </div>
 
    <p>确认配置无误后,点击「立即购买」→ 「去支付」,等待一两分钟服务器就创建好了。</p>
  </div>
 
  <!-- 第二步:配置安全组 -->
  <div class="section" id="step2">
    <h2><span class="step-num">2</span> 配置安全组(开放端口)</h2>
 
    <p>华为云默认只开放了 22 端口(SSH 登录用),我们需要手动开放项目要用的端口。</p>
 
    <h3>2.1 进入安全组配置</h3>
    <ol>
      <li>在 ECS 控制台,找到你的服务器,点击服务器名称进入详情</li>
      <li>找到「安全组」标签页</li>
      <li>点击安全组名称(如 default)进入配置</li>
    </ol>
 
    <h3>2.2 添加入方向规则</h3>
    <p>点击「添加规则」,依次添加以下规则:</p>
 
    <div class="table-wrapper">
      <table>
        <tr><th>优先级</th><th>策略</th><th>协议端口</th><th>类型</th><th>源地址</th><th>说明</th></tr>
        <tr><td>1</td><td>允许</td><td>TCP:22</td><td>IPv4</td><td>0.0.0.0/0</td><td>SSH 远程登录</td></tr>
        <tr><td>1</td><td>允许</td><td>TCP:5174</td><td>IPv4</td><td>0.0.0.0/0</td><td>前端访问端口</td></tr>
        <tr><td>1</td><td>允许</td><td>TCP:8010</td><td>IPv4</td><td>0.0.0.0/0</td><td>后端 API 端口</td></tr>
        <tr><td>1</td><td>允许</td><td>TCP:80</td><td>IPv4</td><td>0.0.0.0/0</td><td>HTTP(后面绑域名用)</td></tr>
        <tr><td>1</td><td>允许</td><td>TCP:443</td><td>IPv4</td><td>0.0.0.0/0</td><td>HTTPS(后面绑域名用)</td></tr>
      </table>
    </div>
 
    <div class="tip">
      源地址填 <span class="highlight">0.0.0.0/0</span> 表示允许所有 IP 访问。如果只有你自己用,可以填你电脑的公网 IP,更安全。
    </div>
  </div>
 
  <!-- 第三步:登录服务器 -->
  <div class="section" id="step3">
    <h2><span class="step-num">3</span> 远程登录服务器</h2>
 
    <h3>Windows 用户:使用 PowerShell</h3>
    <p>按下 <kbd>Win</kbd> + <kbd>X</kbd>,选择「Windows PowerShell」或「终端」,输入:</p>
    <div class="code-block">
<span class="cmd">ssh</span> root@你的服务器公网IP
<span class="comment"># 例如:ssh root@123.45.67.89</span>
    </div>
    <p>第一次连接会提示「是否继续连接」,输入 <span class="highlight">yes</span> 回车,然后输入密码(密码不会显示,输完直接回车)。</p>
 
    <h3>Mac 用户:使用终端</h3>
    <p>打开「终端」App,同样输入:</p>
    <div class="code-block"><span class="cmd">ssh</span> root@你的服务器公网IP</div>
 
    <div class="success">
      登录成功后,你会看到类似 <span class="highlight">root@ecs-xxx:~#</span> 的提示符,说明已经进入服务器了!
    </div>
 
    <div class="tip">
      也可以使用华为云控制台的「远程登录」功能(VNC),在 ECS 详情页点击「远程登录」按钮即可。
    </div>
  </div>
 
  <!-- 第四步:安装环境 -->
  <div class="section" id="step4">
    <h2><span class="step-num">4</span> 安装运行环境</h2>
 
    <p>登录服务器后,依次执行以下命令。每输完一行按回车执行,等待完成再继续。</p>
 
    <h3>4.1 更新系统包</h3>
    <div class="code-block">
<span class="cmd">apt</span> update && <span class="cmd">apt</span> upgrade -y
    </div>
    <p>这一步可能需要几分钟,耐心等待。</p>
 
    <h3>4.2 安装 Python 3 和 pip</h3>
    <div class="code-block">
<span class="cmd">apt</span> install -y python3 python3-venv python3-pip git
    </div>
    <p>验证安装:</p>
    <div class="code-block">
<span class="cmd">python3</span> --version
<span class="comment"># 输出类似 Python 3.10.x 就对了</span>
    </div>
 
    <h3>4.3 安装 Node.js</h3>
    <p>前端构建需要 Node.js,我们安装 LTS 版本:</p>
    <div class="code-block">
<span class="comment"># 下载 Node.js 安装脚本</span>
<span class="cmd">curl</span> -fsSL https://deb.nodesource.com/setup_20.x | bash -
 
<span class="comment"># 安装 Node.js</span>
<span class="cmd">apt</span> install -y nodejs
 
<span class="comment"># 验证安装</span>
<span class="cmd">node</span> --version
<span class="cmd">npm</span> --version
<span class="comment"># 输出 v20.x.x 和 10.x.x 就对了</span>
    </div>
 
    <div class="success">
      环境安装完成!现在有了 Python、Node.js 和 Git,可以部署项目了。
    </div>
  </div>
 
  <!-- 第五步:上传代码 -->
  <div class="section" id="step5">
    <h2><span class="step-num">5</span> 上传项目代码到服务器</h2>
 
    <p>有两种方式上传代码,选择你方便的一种:</p>
 
    <h3>方式一:Git 克隆(推荐,如果代码在 Git 仓库)</h3>
    <p>如果你的代码已经推送到 Gitee / GitHub / GitLab 等平台:</p>
    <div class="code-block">
<span class="cmd">cd</span> /opt
<span class="cmd">git</span> clone 你的仓库地址 score-query
<span class="comment"># 例如:git clone https://gitee.com/你的用户名/score-query.git</span>
 
<span class="cmd">cd</span> score-query
<span class="cmd">ls</span>
<span class="comment"># 应该能看到 backend、frontend、scripts 等目录</span>
    </div>
 
    <h3>方式二:本地上传(适合代码在本地电脑)</h3>
    <p><strong>Windows 用户:</strong>下载 <a href="https://winscp.net/" target="_blank">WinSCP</a> 或使用 <a href="https://www.putty.org/" target="_blank">PuTTY</a> 的 pscp</p>
    <p><strong>Mac 用户:</strong>在本地终端执行:</p>
    <div class="code-block">
<span class="cmd">scp</span> -r /本地项目路径 root@你的服务器IP:/opt/score-query
    </div>
 
    <div class="tip">
      如果你用的是 VS Code,可以安装「Remote - SSH」插件,直接在 VS Code 里编辑服务器上的文件,非常方便!
    </div>
  </div>
 
  <!-- 第六步:安装依赖 -->
  <div class="section" id="step6">
    <h2><span class="step-num">6</span> 安装项目依赖</h2>
 
    <p>假设项目在 <span class="highlight">/opt/score-query</span> 目录下:</p>
 
    <h3>6.1 安装后端 Python 依赖</h3>
    <div class="code-block">
<span class="cmd">cd</span> /opt/score-query/backend
 
<span class="comment"># 创建虚拟环境(推荐,隔离依赖)</span>
<span class="cmd">python3</span> -m venv .venv
 
<span class="comment"># 激活虚拟环境并安装依赖</span>
<span class="cmd">source</span> .venv/bin/activate
<span class="cmd">pip</span> install -r requirements.txt
 
<span class="comment"># 等待安装完成,验证一下</span>
<span class="cmd">python</span> -c "import fastapi; print('FastAPI 版本:', fastapi.__version__)"
    </div>
 
    <h3>6.2 安装前端 Node.js 依赖</h3>
    <div class="code-block">
<span class="cmd">cd</span> /opt/score-query/frontend
 
<span class="comment"># 安装依赖,可能需要几分钟</span>
<span class="cmd">npm</span> install
 
<span class="comment"># 验证</span>
<span class="cmd">npm</span> run build
<span class="comment"># 如果能成功构建出 dist 目录就说明没问题</span>
    </div>
 
    <div class="warning">
      如果 <span class="highlight">npm install</span> 很慢,可以切换到国内镜像:<br>
      <span class="highlight">npm config set registry https://registry.npmmirror.com</span>
    </div>
  </div>
 
  <!-- 第七步:初始化 -->
  <div class="section" id="step7">
    <h2><span class="step-num">7</span> 初始化数据库与管理员</h2>
 
    <h3>7.1 创建数据目录</h3>
    <div class="code-block">
<span class="cmd">cd</span> /opt/score-query/backend
<span class="cmd">mkdir</span> -p data uploads
    </div>
 
    <h3>7.2 初始化管理员账号</h3>
    <div class="code-block">
<span class="cmd">cd</span> /opt/score-query/backend
<span class="cmd">source</span> .venv/bin/activate
<span class="cmd">python</span> scripts/seed_admin.py
    </div>
 
    <div class="success">
      默认账号:<br>
      管理员:<span class="highlight">admin</span> / 密码:<span class="highlight">admin123</span><br>
      阅卷员:<span class="highlight">grader</span> / 密码:<span class="highlight">grader123</span><br>
      <strong>上线后请务必修改默认密码!</strong>
    </div>
  </div>
 
  <!-- 第八步:启动服务 -->
  <div class="section" id="step8">
    <h2><span class="step-num">8</span> 启动服务并验证</h2>
 
    <h3>8.1 使用项目自带脚本启动</h3>
    <p>项目已经提供了方便的启动脚本:</p>
    <div class="code-block">
<span class="cmd">cd</span> /opt/score-query
 
<span class="comment"># 给脚本添加执行权限(首次需要)</span>
<span class="cmd">chmod</span> +x scripts/*.sh
 
<span class="comment"># 启动全部服务(生产模式)</span>
./scripts/start.sh
    </div>
 
    <p>启动成功后,你会看到类似输出:</p>
    <div class="code-block">
后端已启动 → http://127.0.0.1:8010
前端生产 → http://0.0.0.0:5174
    </div>
 
    <h3>8.2 验证服务是否正常</h3>
    <p>检查进程是否在运行:</p>
    <div class="code-block">
./scripts/status.sh
<span class="comment"># 或</span>
<span class="cmd">ps</span> aux | grep uvicorn
<span class="cmd">ps</span> aux | grep vite
    </div>
 
    <p>检查端口是否监听:</p>
    <div class="code-block">
<span class="cmd">ss</span> -tlnp | grep -E '5174|8010'
    </div>
 
    <h3>8.3 在浏览器访问</h3>
    <p>打开浏览器,访问:</p>
    <div class="code-block">
http://你的服务器公网IP:5174
    </div>
 
    <div class="success">
      如果能看到登录页面,输入 <span class="highlight">admin / admin123</span> 能成功登录,那么恭喜你,部署成功了!🎉
    </div>
 
    <h3>8.4 常用命令</h3>
    <div class="table-wrapper">
      <table>
        <tr><th>命令</th><th>作用</th></tr>
        <tr><td><span class="highlight">./scripts/start.sh</span></td><td>启动全部服务(生产模式)</td></tr>
        <tr><td><span class="highlight">./scripts/stop.sh</span></td><td>停止全部服务</td></tr>
        <tr><td><span class="highlight">./scripts/restart.sh</span></td><td>重启全部服务</td></tr>
        <tr><td><span class="highlight">./scripts/status.sh</span></td><td>查看服务状态</td></tr>
        <tr><td><span class="highlight">./scripts/build-frontend.sh</span></td><td>重新构建前端</td></tr>
      </table>
    </div>
 
    <h3>8.5 查看日志</h3>
    <div class="code-block">
<span class="comment"># 后端日志</span>
<span class="cmd">tail</span> -f logs/backend.log
 
<span class="comment"># 前端日志</span>
<span class="cmd">tail</span> -f logs/frontend.log
    </div>
    <p>按 <kbd>Ctrl</kbd> + <kbd>C</kbd> 退出日志查看。</p>
  </div>
 
  <!-- 第九步:开机自启 -->
  <div class="section" id="step9">
    <h2><span class="step-num">9</span> 配置开机自启(强烈推荐)</h2>
 
    <p>前面的方式启动后,如果服务器重启,服务不会自动恢复。我们配置 systemd 服务来实现开机自启。</p>
 
    <h3>9.1 创建后端服务</h3>
    <div class="code-block">
<span class="cmd">cat</span> > /etc/systemd/system/score-backend.service << 'EOF'
[Unit]
Description=Score Query Backend (FastAPI)
After=network.target
 
[Service]
Type=simple
User=root
WorkingDirectory=/opt/score-query/backend
ExecStart=/opt/score-query/backend/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8010
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
 
[Install]
WantedBy=multi-user.target
EOF
    </div>
 
    <h3>9.2 创建前端服务</h3>
    <div class="code-block">
<span class="cmd">cat</span> > /etc/systemd/system/score-frontend.service << 'EOF'
[Unit]
Description=Score Query Frontend (Vite Preview)
After=network.target
 
[Service]
Type=simple
User=root
WorkingDirectory=/opt/score-query/frontend
ExecStart=/usr/bin/npm run preview
Restart=always
RestartSec=5
 
[Install]
WantedBy=multi-user.target
EOF
    </div>
 
    <h3>9.3 启用并启动服务</h3>
    <div class="code-block">
<span class="comment"># 先停止之前的脚本启动的服务</span>
<span class="cmd">cd</span> /opt/score-query && ./scripts/stop.sh
 
<span class="comment"># 重新加载 systemd 配置</span>
<span class="cmd">systemctl</span> daemon-reload
 
<span class="comment"># 设置开机自启</span>
<span class="cmd">systemctl</span> enable score-backend score-frontend
 
<span class="comment"># 启动服务</span>
<span class="cmd">systemctl</span> start score-backend score-frontend
 
<span class="comment"># 查看状态</span>
<span class="cmd">systemctl</span> status score-backend
<span class="cmd">systemctl</span> status score-frontend
    </div>
 
    <div class="success">
      看到 <span class="highlight">active (running)</span> 就说明配置成功了!服务器重启后服务会自动启动。
    </div>
 
    <h3>9.4 systemd 常用命令</h3>
    <div class="table-wrapper">
      <table>
        <tr><th>命令</th><th>作用</th></tr>
        <tr><td>systemctl start 服务名</td><td>启动服务</td></tr>
        <tr><td>systemctl stop 服务名</td><td>停止服务</td></tr>
        <tr><td>systemctl restart 服务名</td><td>重启服务</td></tr>
        <tr><td>systemctl status 服务名</td><td>查看服务状态</td></tr>
        <tr><td>journalctl -u 服务名 -f</td><td>查看服务日志</td></tr>
      </table>
    </div>
  </div>
 
  <!-- 第十步:绑定域名 -->
  <div class="section" id="step10">
    <h2><span class="step-num">10</span> 绑定域名(可选进阶)</h2>
 
    <p>用 IP 访问不太方便,也不专业。我们可以绑定一个域名。</p>
 
    <h3>10.1 购买域名</h3>
    <p>在华为云「域名注册」服务购买一个域名,例如 <span class="highlight">yourdomain.com</span>。</p>
 
    <h3>10.2 配置 DNS 解析</h3>
    <ol>
      <li>进入「云解析服务 DNS」</li>
      <li>找到你的域名,点击「解析」</li>
      <li>添加记录:</li>
    </ol>
    <div class="table-wrapper">
      <table>
        <tr><th>主机记录</th><th>类型</th><th>值</th></tr>
        <tr><td>grader(或 www)</td><td>A</td><td>你的服务器公网IP</td></tr>
      </table>
    </div>
    <p>等待几分钟解析生效。</p>
 
    <h3>10.3 修改 CORS 配置</h3>
    <p>因为项目有 CORS 跨域限制,需要把你的域名加到允许列表中。创建环境变量配置:</p>
    <div class="code-block">
<span class="cmd">cat</span> > /opt/score-query/.env << 'EOF'
CORS_ORIGINS="http://grader.yourdomain.com,https://grader.yourdomain.com,http://localhost:5174"
JWT_SECRET="这里改成一个随机的长字符串,比如 mashuang123456!@#"
EOF
    </div>
 
    <p>然后修改 systemd 服务加载这个环境变量:</p>
    <div class="code-block">
<span class="cmd">sed</span> -i '/ExecStart/i EnvironmentFile=/opt/score-query/.env' /etc/systemd/system/score-backend.service
<span class="cmd">systemctl</span> daemon-reload
<span class="cmd">systemctl</span> restart score-backend
    </div>
 
    <h3>10.4 修改前端 vite 配置中的 allowedHosts</h3>
    <p>编辑 <span class="highlight">/opt/score-query/frontend/vite.config.ts</span>,把你的域名加到 <span class="highlight">allowedHosts</span> 数组中,然后重新构建:</p>
    <div class="code-block">
<span class="cmd">cd</span> /opt/score-query/frontend
<span class="cmd">npm</span> run build
<span class="cmd">systemctl</span> restart score-frontend
    </div>
 
    <h3>10.5 配置 Nginx 反向代理(推荐)</h3>
    <p>用 Nginx 做反向代理,可以直接用 80 端口访问,不需要加端口号:</p>
 
    <div class="code-block">
<span class="comment"># 安装 Nginx</span>
<span class="cmd">apt</span> install -y nginx
 
<span class="comment"># 创建配置文件</span>
<span class="cmd">cat</span> > /etc/nginx/sites-available/score-query << 'EOF'
server {
    listen 80;
    server_name grader.yourdomain.com;
 
    # 前端静态文件(也可以继续用 vite preview,这里演示直接由 Nginx 托管)
    location / {
        proxy_pass http://127.0.0.1:5174;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
EOF
 
<span class="comment"># 启用站点</span>
<span class="cmd">ln</span> -s /etc/nginx/sites-available/score-query /etc/nginx/sites-enabled/
<span class="cmd">rm</span> /etc/nginx/sites-enabled/default
 
<span class="comment"># 测试配置</span>
<span class="cmd">nginx</span> -t
 
<span class="comment"># 重载 Nginx</span>
<span class="cmd">systemctl</span> reload nginx
    </div>
 
    <p>现在就可以用 <span class="highlight">http://grader.yourdomain.com</span> 直接访问了!</p>
 
    <div class="tip">
      如果需要 HTTPS,可以用 Let's Encrypt 免费证书,安装 certbot 后执行:<br>
      <span class="highlight">apt install -y certbot python3-certbot-nginx && certbot --nginx</span>
    </div>
  </div>
 
  <!-- FAQ -->
  <div class="section" id="faq">
    <h2><span class="step-num">❓</span> 常见问题 FAQ</h2>
 
    <div class="faq-item">
      <div class="faq-q">Q1: 浏览器访问不了怎么办?</div>
      <div class="faq-a">
        <p>按以下顺序排查:</p>
        <ol>
          <li>确认安全组是否开放了 5174 端口</li>
          <li>确认服务是否在运行:<span class="highlight">systemctl status score-backend score-frontend</span></li>
          <li>在服务器本地测试:<span class="highlight">curl http://127.0.0.1:5174</span>,能返回 HTML 说明服务正常</li>
          <li>如果本地通、外网不通,基本是安全组或防火墙的问题</li>
          <li>检查系统防火墙:<span class="highlight">ufw status</span>,如果开了就 <span class="highlight">ufw allow 5174</span></li>
        </ol>
      </div>
    </div>
 
    <div class="faq-item">
      <div class="faq-q">Q2: 前端页面打开了,但登录/接口报错?</div>
      <div class="faq-a">
        <p>大概率是后端没启动或跨域配置问题:</p>
        <ol>
          <li>确认后端在运行:<span class="highlight">systemctl status score-backend</span></li>
          <li>本地测试后端:<span class="highlight">curl http://127.0.0.1:8010/docs</span>,能看到 Swagger 文档说明正常</li>
          <li>如果是跨域错误(浏览器 F12 控制台有 CORS 字样),检查 CORS_ORIGINS 配置是否包含你的访问地址</li>
        </ol>
      </div>
    </div>
 
    <div class="faq-item">
      <div class="faq-q">Q3: SQLite 数据库文件在哪?怎么备份?</div>
      <div class="faq-a">
        <p>数据库文件在 <span class="highlight">/opt/score-query/backend/data/app.db</span>,直接复制这个文件就是备份。建议定期备份:</p>
        <div class="code-block"><span class="cmd">cp</span> /opt/score-query/backend/data/app.db /opt/score-query/backend/data/app.db.bak.$(date +%Y%m%d)</div>
      </div>
    </div>
 
    <div class="faq-item">
      <div class="faq-q">Q4: 修改了代码怎么更新?</div>
      <div class="faq-a">
        <p>分两种情况:</p>
        <ul>
          <li><strong>改了前端代码:</strong>重新构建 → 重启前端
            <div class="code-block"><span class="cmd">cd</span> /opt/score-query/frontend && npm run build && systemctl restart score-frontend</div>
          </li>
          <li><strong>改了后端代码:</strong>直接重启后端
            <div class="code-block"><span class="cmd">systemctl</span> restart score-backend</div>
          </li>
        </ul>
      </div>
    </div>
 
    <div class="faq-item">
      <div class="faq-q">Q5: 服务器太卡了怎么办?</div>
      <div class="faq-a">
        <p>可以在华为云控制台「变更规格」升级配置。也可以优化:</p>
        <ul>
          <li>用 Nginx 直接托管前端静态文件,关掉 vite preview(节省内存)</li>
          <li>开启 gzip 压缩</li>
          <li>如果数据量大,可以考虑把 SQLite 换成 MySQL</li>
        </ul>
      </div>
    </div>
 
    <div class="faq-item">
      <div class="faq-q">Q6: 忘记管理员密码怎么办?</div>
      <div class="faq-a">
        <p>重新运行 seed_admin.py 脚本会重置默认账号密码:</p>
        <div class="code-block">
<span class="cmd">cd</span> /opt/score-query/backend
<span class="cmd">source</span> .venv/bin/activate
<span class="cmd">python</span> scripts/seed_admin.py
        </div>
      </div>
    </div>
 
    <div class="faq-item">
      <div class="faq-q">Q7: 用脚本启动还是 systemd 启动?</div>
      <div class="faq-a">
        <p>推荐用 <strong>systemd</strong>,因为:</p>
        <ul>
          <li>开机自动启动,服务器重启不怕</li>
          <li>进程崩溃自动重启</li>
          <li>统一的日志管理(journalctl)</li>
          <li>更专业、更稳定</li>
        </ul>
        <p>脚本方式适合临时调试使用。</p>
      </div>
    </div>
  </div>
 
  <div class="footer">
    <p>📚 考试阅卷系统华为云部署教程 | 祝你部署顺利!🎉</p>
  </div>
</div>
</body>
</html>