​​​​​​功能详解 01 · 用户注册:从表单到数据库一行
个人主页会编程的土豆欢迎来访作者简介后端学习者❄️个人专栏数据结构与算法数据库leetcode✨那些你一个人走过的夜路终将化作照亮未来的光系列《影院票务 GO》功能详解 · 第 01 篇对应代码代码在这个系列的文章中templates/register.html→static/js/app.js→handlers/auth.go→dao/user.go→utils/password.go数据库sql/schema.sql中users表前置一、背景为什么要有注册影院票务系统TTMS里很多操作必须知道「你是谁」选座、下单要把座位绑到你的账号订单列表只显示自己的票评论、评分要记录发表人。注册就是在系统里新建一条用户记录以后用「用户名 密码」登录。本项目的注册特点前后端分离式交互页面是 HTML 表单提交走JSON API不是传统 form POST 刷新整页。密码绝不存明文每个用户随机8 位盐存MD5(密码盐)。新用户默认顾客role 0管理员只能种子数据或人工改库注册接口不会创建管理员。二、用户视角点「注册」后发生了什么打开http://localhost:8080/register端口以config为准你会看到用户名必填密码必填HTML 限制至少 6 位昵称、手机可选填完点「注册并登录」前端脚本doRegister会阻止表单默认刷新e.preventDefault()把字段打包成 JSONPOST /api/register注册成功后立刻再调POST /api/login自动登录跳转到首页/。若用户名已存在或密码太短浏览器alert弹出后端返回的中文错误信息。三、请求链路总览[浏览器 register.html] │ 用户提交 ▼ [app.js doRegister] │ fetch POST /api/register (JSON body) ▼ [handlers/routes.go] mux.HandleFunc(/api/register, a.APIRegister) ▼ [handlers/auth.go APIRegister] │ 校验参数 → 查重 → 哈希密码 → 组装 User ▼ [dao/user.go CreateUser] │ INSERT INTO users ... ▼ [MySQL users 表] │ 返回 LastInsertId ▼ [utils/json.go OK] → {code:0,message:ok,data:{id:3}} │ ▼ (前端继续) [app.js] POST /api/login → Session → location.href/路由注册在handlers/routes.go第 18 行mux.HandleFunc(/api/register, a.APIRegister)页面路由mux.HandleFunc(/register, a.PageRegister)PageRegister只负责渲染模板不做业务func (a *App) PageLogin(w http.ResponseWriter, r *http.Request) { ... } func (a *App) PageRegister(w http.ResponseWriter, r *http.Request) { a.Render(w, register.html, pageData{Title: 注册, User: a.currentUser(r)}) }四、前端表单如何变成 JSONtemplates/register.html核心form idregForm onsubmitreturn doRegister(event) label用户名input nameusername required //label label密码input namepassword typepassword required minlength6 //label ... /formstatic/js/app.js中doRegisterasync function doRegister(e) { e.preventDefault(); const fd new FormData(e.target); try { await api(/api/register, { method: POST, body: JSON.stringify({ username: fd.get(username), password: fd.get(password), nickname: fd.get(nickname), phone: fd.get(phone), }), }); await api(/api/login, { ... }); // 注册成功后自动登录 location.href /; } catch (err) { alert(err.message); } return false; }公共封装api()做了三件事fetch时带Content-Type: application/jsoncredentials: same-origin—— 为后续登录写 Cookie 做准备解析统一格式{code, message, data}code ! 0就抛错。对应后端utils/json.gotype APIResult struct { Code int json:code Message string json:message Data interface{} json:data,omitempty } func OK(w http.ResponseWriter, data interface{}) { JSON(w, 0, ok, data) } func Fail(w http.ResponseWriter, message string) { JSON(w, 1, message, nil) }设计要点业务失败时 HTTP 状态码仍是 200靠code1区分前端只需看data.code不必处理 4xx/5xx 分支课设简化写法。五、后端 APIRegister逐步拆解完整代码在handlers/auth.go。我们按执行顺序读。5.1 只允许 POSTif r.Method ! http.MethodPost { utils.Fail(w, 方法不允许) return }若有人用浏览器直接访问/api/registerGET会收到 JSON 错误不会误插入数据。5.2 解析 JSON 到匿名结构体var req struct { Username string json:username Password string json:password Nickname string json:nickname Phone string json:phone } if err : utils.DecodeJSON(r, req); err ! nil { utils.Fail(w, 参数错误) return }DecodeJSON读r.Body并json.Decode。字段 tag 的json:username与前端 JSON 键名对应。5.3 参数校验req.Username strings.TrimSpace(req.Username) if req.Username || len(req.Password) 6 { utils.Fail(w, 用户名不能为空密码至少6位) return }去掉用户名首尾空格避免 alice 这种视觉重复账号密码长度6是后端底线前端minlength6是第一道防线后端必须再验防绕过。5.4 用户名唯一性exist, err : dao.GetUserByUsername(req.Username) if err ! nil { utils.Fail(w, err.Error()) return } if exist ! nil { utils.Fail(w, 用户名已存在) return }dao/user.gofunc GetUserByUsername(username string) (*models.User, error) { u : models.User{} err : db.DB.QueryRow( SELECT id,username,password,salt,nickname,phone,role,created_at FROM users WHERE username?, username, ).Scan(...) if errors.Is(err, sql.ErrNoRows) { return nil, nil // 没找到不是错误表示可以注册 } return u, err }sql.ErrNoRows时返回(nil, nil)是 Go database/sql 的常见模式「查无此人」≠ 数据库故障。表结构里还有一层保险username VARCHAR(50) NOT NULL UNIQUE,即使并发两次相同用户名注册第二条 INSERT 也会因唯一索引失败本项目先查再插课设足够高并发应靠数据库约束 事务。5.5 昵称默认值if req.Nickname { req.Nickname req.Username }用户没填昵称时展示名等于登录名。5.6 密码哈希盐 MD5salt : utils.RandomSalt(8) u : models.User{ Username: req.Username, Password: utils.HashPassword(req.Password, salt), Salt: salt, Nickname: req.Nickname, Phone: req.Phone, Role: models.RoleCustomer, // 常量 0 }utils/password.gofunc HashPassword(password, salt string) string { sum : md5.Sum([]byte(password salt)) return hex.EncodeToString(sum[:]) } func RandomSalt(n int) string { const letters abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 // crypto/rand 逐字符随机 ... }为什么要盐若只存MD5(密码)两个用户密码相同则哈希相同攻击者可用彩虹表批量反查。每人不同盐同样密码哈希也不同。种子数据验证方式sql/seed.sql(admin, MD5(CONCAT(admin123, saltadmin)), saltadmin, ...),MySQL 的MD5(CONCAT(...))与 Go 的md5.Sum([]byte(passwordsalt))算法一致。答辩提醒MD5 已不适合生产密码存储应 bcrypt/argon2课设选 MD5 是为了算法简单、与 SQL 种子一致。详见blogs/13-密码与哈希MD5与盐.md。5.7 写入数据库if err : dao.CreateUser(u); err ! nil { utils.Fail(w, 注册失败: err.Error()) return } utils.OK(w, map[string]interface{}{id: u.ID})dao/user.gofunc CreateUser(u *models.User) error { res, err : db.DB.Exec( INSERT INTO users(username,password,salt,nickname,phone,role) VALUES(?,?,?,?,?,?), u.Username, u.Password, u.Salt, u.Nickname, u.Phone, u.Role, ) if err ! nil { return err } u.ID, _ res.LastInsertId() return nil }LastInsertId()把数据库自增主键写回u.ID响应里带给前端前端当前没用 id但 API 设计完整。models.User中密码字段带json:-即使将来把 User 序列化给前端也不会泄露哈希type User struct { Password string json:- Salt string json:- Role int json:role ... }六、数据库表与字段含义sql/schema.sqlCREATE TABLE IF NOT EXISTS users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password CHAR(32) NOT NULL COMMENT MD5(passwordsalt), salt VARCHAR(16) NOT NULL, nickname VARCHAR(50) NOT NULL DEFAULT , phone VARCHAR(20) NOT NULL DEFAULT , role TINYINT NOT NULL DEFAULT 0, -- 0顾客 1管理员 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );字段注册时谁赋值说明id数据库自增用户唯一标识username用户输入登录名passwordHashPassword32 位十六进制saltRandomSalt(8)明文存储用于登录校验nickname用户或默认用户名页面展示phone用户可选联系方式role固定RoleCustomer0注册不能当管理员created_at数据库默认注册时间七、注册 vs 登录别混淆环节注册/api/register登录/api/login密码处理生成盐 → 哈希 →存入读库盐 → 哈希输入 →比对Session不创建Sessions.Create写 Cookie返回{id}{id, username, nickname, role}注册成功后前端主动调登录用户无感若只注册不登录首页仍显示「登录/注册」链接。登录校验用utils.CheckPasswordfunc CheckPassword(password, salt, hashed string) bool { return HashPassword(password, salt) hashed }同一套HashPassword保证注册与登录算法一致。八、常见错误与排查现象可能原因怎么查alert「参数错误」JSON 格式不对、Body 空浏览器 Network 看 Request Payload「用户名已存在」重复注册SELECT * FROM users WHERE username...「注册失败: …」数据库未启动、DSN 错看main.go启动日志注册成功但未登录第二步 login 失败Network 看/api/login响应密码 5 位也能提交只绕过前端后端应拒绝本项目已校验len 6九、小结页面register.html收集字段JS转 JSON POST 到/api/register。APIRegister校验 → 查重 →RandomSaltHashPassword→CreateUserINSERT。新用户role 固定为 0顾客密码MD5(明文盐)存库明文永不落盘。前端注册成功后自动 login建立 Session下一篇细讲。统一 JSON 响应code/message/data前后端约定清晰。用一句话向学长描述「注册就是 handlers 收 JSONdao 插 users 一行密码先随机盐再 MD5库里只有摘要。」下一篇02-登录退出与Session.md—— 登录如何写 Cookiettms_sidstore/session.go如何在内存里记住你。

相关新闻

目前 Claude / GPT 的订阅建议与反代避坑

目前 Claude / GPT 的订阅建议与反代避坑

半年来在 Claude 和 GPT 的 CLI 上累计消耗了约 30,000$ 用量的 tokens(Claude : GPT 2: 1,Fable 5 出来之后 GPT 用量为 0)。这期间关于模型对比体验的文章断断续续其实写了不少,但 Agent 相关技术和概念迭代太快,新…

2026/9/3 9:25:26 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-@Extend 的使用

HarmonyOS应用开发实战:猫猫大作战-@Extend 的使用

前言 Extend 装饰器用于为特定组件类型扩展样式方法,与 Styles 的通用属性集合不同,Extend 可以访问组件特有的属性。 本文以「猫猫大作战」的 Text 标题样式为锚点,讲解 Extend 的使用。 提示:本系列不讲 ArkTS 基础语法与环境…

2026/9/3 9:26:20 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-在 HarmonyOS 应用中,`app.json5` 位于 `AppScope/` 目录下,是整个应用的“全

HarmonyOS应用开发实战:猫猫大作战-在 HarmonyOS 应用中,`app.json5` 位于 `AppScope/` 目录下,是整个应用的“全

前言 在 HarmonyOS 应用中,app.json5 位于 AppScope/ 目录下,是整个应用的“全球身份证“——它定义了应用的唯一标识(bundleName)、版本号、全局图标和标签。系统包管理器和应用市场都依赖这个文件的配置来识别和分发应用。 本…

2026/8/26 20:06:05 阅读更多 →

最新新闻

STM32F103定时器硬同步设计:TIM2-ADC触发与系统时序闭环

STM32F103定时器硬同步设计:TIM2-ADC触发与系统时序闭环

简介:本资源是2024年全国大学生电子设计竞赛H题的完整嵌入式解决方案,面向电子、自动化、通信等专业本科生及电赛备赛团队,聚焦信号采集、实时处理与硬件闭环控制等典型赛题任务。项目以STM32F103系列微控制器为核心平台,采用标准…

2026/9/4 9:06:25 阅读更多 →
Apache OSSIE:基于SCA的软件无线电组件化开发框架详解

Apache OSSIE:基于SCA的软件无线电组件化开发框架详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/4 9:06:25 阅读更多 →
AI Agent 面试题 284:如何设计Agent的Prompt性能基准测试?

AI Agent 面试题 284:如何设计Agent的Prompt性能基准测试?

🔥 AI Agent 面试题 284:如何设计Agent的Prompt性能基准测试?摘要:本文深入解析了「如何设计Agent的Prompt性能基准测试?」这一 AI Agent 领域的核心面试题。文章从 Prompt 模板管理 的基本概念出发,系统性…

2026/9/4 9:06:25 阅读更多 →
SOT-23 P沟道MOS管选型指南:核心参数与常见料号解析

SOT-23 P沟道MOS管选型指南:核心参数与常见料号解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/4 9:06:25 阅读更多 →
AI Agent 面试题 279:Prompt注入防御的成本与安全性如何平衡?

AI Agent 面试题 279:Prompt注入防御的成本与安全性如何平衡?

🔥 AI Agent 面试题 279:Prompt注入防御的成本与安全性如何平衡?摘要:本文深入解析了「Prompt注入防御的成本与安全性如何平衡?」这一 AI Agent 领域的核心面试题。文章从 Prompt 注入防御 的基本概念出发,…

2026/9/4 9:06:25 阅读更多 →
AI Agent 面试题 314:如何实现Agent的API调用结果聚合和转换?

AI Agent 面试题 314:如何实现Agent的API调用结果聚合和转换?

🔥 AI Agent 面试题 314:如何实现Agent的API调用结果聚合和转换?摘要:本文深入解析了「如何实现Agent的API调用结果聚合和转换?」这一 AI Agent 领域的核心面试题。文章从 API 编排与链式调用 的基本概念出发&#xff…

2026/9/4 9:05:23 阅读更多 →

日新闻

ESP32S2嵌入式收音机全栈开发实战指南

ESP32S2嵌入式收音机全栈开发实战指南

简介:本资源是一个基于ESP32-S2芯片的嵌入式综合实践项目,面向本科毕业设计、课程设计及实训开发人员,聚焦网络收音机与FM收音机双模功能实现,融合ESP-IDF框架、ESP-ADF音频开发库与LVGL图形界面库,具备完整软硬件协同…

2026/9/4 0:00:28 阅读更多 →
WorkBuddy+Python实战:从零搭建商品库存管理系统

WorkBuddy+Python实战:从零搭建商品库存管理系统

最近想自己动手做一个“商品库存管理系统”的人变多了。很多开网店、做小团队ERP选型、或者刚学Python的读者,不是不想用系统,而是被传统开发路径劝退了:要装数据库,要写后端接口,要学前端页面,还要考虑多人…

2026/9/4 0:00:28 阅读更多 →
旅游情感分析:基于Python的垂直场景深度解析

旅游情感分析:基于Python的垂直场景深度解析

简介:本资源是一份面向计算机专业本科生的毕业设计实践项目,聚焦旅游行业真实场景,解决旅游平台对用户评论情感倾向自动识别与管理的需求。系统基于Python 3.9.11与Anaconda环境构建,集成携程、马蜂窝双平台爬虫模块,并…

2026/9/4 0:00:28 阅读更多 →

周新闻

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

每年校招季我都会接触不少准备数据库方向笔试的同学,看到最多的状态就是:简历上写着“熟悉 MySQL”“了解索引优化”,一碰到数据库管理工程师的笔试卷,却在索引、事务、锁、备份恢复这些题目上翻车。网易这套 2018 校园招聘数据库…

2026/9/3 4:22:22 阅读更多 →
数字电路时序基石:深入理解建立时间与保持时间

数字电路时序基石:深入理解建立时间与保持时间

1. 这不是“背公式”的事:时间参数到底在约束什么你翻过数字电路教材,一定见过这两个词:建立时间(Setup Time)和保持时间(Hold Time)。它们常被并列写在触发器(Flip-Flop&#xff09…

2026/9/3 4:22:01 阅读更多 →
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

1. 项目缘起:从赛题到超声波测距机的诞生第八届蓝桥杯单片机设计与开发国赛的题目,我至今记忆犹新。它没有直接给出一个花哨的名字,而是用“超声波测距机”这个朴实无华的功能描述,精准地勾勒出了考核的核心。对于当时备赛的我而言…

2026/9/3 4:22:59 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/3 4:17:49 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/3 4:18:56 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/3 4:21:44 阅读更多 →