功能详解 02 · 登录、退出与 Session:Cookie ttms_sid 如何认人
个人主页会编程的土豆欢迎来访作者简介后端学习者❄️个人专栏数据结构与算法数据库leetcode✨那些你一个人走过的夜路终将化作照亮未来的光系列《影院票务 GO》功能详解 · 第 02 篇对应代码handlers/auth.go·store/session.go·handlers/app.go·static/js/app.js前置第 01 篇注册知识点篇blogs/12-Cookie与Session登录态.md一、背景HTTP 无状态网站怎么「记住你」HTTP 协议本身是无状态的服务器处理完一次请求就「忘了」你是谁但购票系统需要连续认人——从登录到选座、下单不能每点一次按钮都重新输入密码。常见方案Session会话 Cookie浏览器存 Session ID。本项目约定项目值Cookie 名ttms_sidCookie 值32 字节随机数的 hex 字符串Session ID服务端存储内存map[string]*sessionData有效期24 小时NewSessionStore(24 * time.Hour)HttpOnlytrueJS 读不到防 XSS 偷 Cookie重要区分认证Authentication登录时验证用户名密码 ——APILogin。识别Identification之后每次请求用 Cookie 找 Session ——Sessions.Get。鉴权Authorization识别后检查能不能做某操作 —— 第 03 篇的requireAdmin。本篇聚焦登录 / 退出 / Session 读写。二、用户视角登录打开/login输入user/user123或admin/admin123。点登录 → 页面跳转管理员 →/admin顾客 →/顶栏显示昵称、「订单」「退出」管理员还多一个「后台」。退出点「退出」→ 调/api/logout→ 回首页顶栏变回「登录 / 注册」。刷新页面为什么仍登录因为浏览器自动带上 Cookiettms_sid服务端用 ID 查内存 map取出*models.User。三、完整流程图3.1 登录成功[浏览器 POST /api/login {username, password}] ▼ [APILogin] GetUserByUsername ▼ [CheckPassword] 比对 MD5(输入库中salt) 与 库中password ▼ [Sessions.Create(w, user)] ├─ newID() → sessionId ├─ map[sessionId] {User, ExpiresAt} └─ Set-Cookie: ttms_sidsessionId; HttpOnly; MaxAge86400 ▼ [utils.OK] 返回 {id, username, nickname, role} ▼ [app.js doLogin] role1 ? /admin : /3.2 后续任意请求[浏览器 GET /orders Cookie: ttms_sidabc...] ▼ [handler] a.currentUser(r) ▼ [Sessions.Get(r)] 读 Cookie → 查 map → 未过期则 *User ▼ [Render / 业务逻辑] 模板里 {{.User.Nickname}}3.3 退出[POST /api/logout] ▼ [Sessions.Destroy] ├─ delete(map, sessionId) └─ Set-Cookie: ttms_sid; MaxAge-1 让浏览器删 Cookie ▼ [utils.OK]四、路由与 Handlerhandlers/routes.gomux.HandleFunc(/login, a.PageLogin) mux.HandleFunc(/api/login, a.APILogin) mux.HandleFunc(/api/logout, a.APILogout) mux.HandleFunc(/api/me, a.APIMe)4.1 APILoginhandlers/auth.gofunc (a *App) APILogin(w http.ResponseWriter, r *http.Request) { if r.Method ! http.MethodPost { utils.Fail(w, 方法不允许) return } var req struct { Username string json:username Password string json:password } if err : utils.DecodeJSON(r, req); err ! nil { utils.Fail(w, 参数错误) return } u, err : dao.GetUserByUsername(strings.TrimSpace(req.Username)) if err ! nil { utils.Fail(w, err.Error()) return } if u nil || !utils.CheckPassword(req.Password, u.Salt, u.Password) { utils.Fail(w, 用户名或密码错误) return } a.Sessions.Create(w, u) utils.OK(w, map[string]interface{}{ id: u.ID, username: u.Username, nickname: u.Nickname, role: u.Role, }) }安全细节用户不存在和密码错误同一句话「用户名或密码错误」—— 避免攻击者枚举哪些用户名已注册。校验通过后才CreateSession失败不写 Cookie。4.2 APILogoutfunc (a *App) APILogout(w http.ResponseWriter, r *http.Request) { a.Sessions.Destroy(w, r) utils.OK(w, nil) }4.3 APIMe —— 当前登录用户是谁func (a *App) APIMe(w http.ResponseWriter, r *http.Request) { u : a.currentUser(r) if u nil { utils.Fail(w, 未登录) return } utils.OK(w, u) }可用于前端 SPA 拉用户信息本项目主要在服务端模板里注入User。五、SessionStore 源码精读文件store/session.go。5.1 数据结构与常量const cookieName ttms_sid type sessionData struct { User *models.User ExpiresAt time.Time } type SessionStore struct { mu sync.RWMutex data map[string]*sessionData ttl time.Duration secure bool }dataSession ID → 用户 过期时间。mu读写锁多个 HTTP 请求并发访问 map 必须加锁否则 race condition。ttlSession 寿命App 初始化时传24 * time.Hour。handlers/app.gofunc NewApp(cfg config.Config) *App { return App{ Cfg: cfg, Sessions: store.NewSessionStore(24 * time.Hour), } }5.2 Create —— 登录写 Sessionfunc (s *SessionStore) Create(w http.ResponseWriter, user *models.User) { id : newID() s.mu.Lock() s.data[id] sessionData{User: user, ExpiresAt: time.Now().Add(s.ttl)} s.mu.Unlock() http.SetCookie(w, http.Cookie{ Name: cookieName, Value: id, Path: /, HttpOnly: true, MaxAge: int(s.ttl.Seconds()), }) }newID()func newID() string { b : make([]byte, 16) _, _ rand.Read(b) return hex.EncodeToString(b) // 32 个 hex 字符 }Cookie 字段解释字段作用Namettms_sid项目自定义避免与别的站冲突Path/全站有效HttpOnlydocument.cookie读不到MaxAge秒数浏览器到期自动删响应头类似Set-Cookie: ttms_sida1b2c3...; Path/; MaxAge86400; HttpOnly注意Session 里存的是*models.User指针含密码哈希字段——只在服务端内存不发给浏览器。5.3 Get —— 每次请求认人func (s *SessionStore) Get(r *http.Request) *models.User { c, err : r.Cookie(cookieName) if err ! nil || c.Value { return nil } s.mu.RLock() defer s.mu.RUnlock() item, ok : s.data[c.Value] if !ok || time.Now().After(item.ExpiresAt) { return nil } return item.User }三种「未登录」情况统一返回nil没有 CookieSession ID 不在 map服务端重启、被 Destroy、GC 清掉已过期。handlers/app.go封装func (a *App) currentUser(r *http.Request) *models.User { return a.Sessions.Get(r) }模板templates/header.html{{if .User}} span classuser{{.User.Nickname}}/span button onclicklogout()退出/button {{else}} a href/login登录/a {{end}}5.4 Destroy —— 退出func (s *SessionStore) Destroy(w http.ResponseWriter, r *http.Request) { c, err : r.Cookie(cookieName) if err nil { s.mu.Lock() delete(s.data, c.Value) s.mu.Unlock() } http.SetCookie(w, http.Cookie{Name: cookieName, Value: , Path: /, MaxAge: -1}) }服务端删 map 浏览器删 Cookie双保险。5.5 过期清理 gcLoopfunc (s *SessionStore) gcLoop() { t : time.NewTicker(10 * time.Minute) for range t.C { now : time.Now() s.mu.Lock() for k, v : range s.data { if now.After(v.ExpiresAt) { delete(s.data, k) } } s.mu.Unlock() } }NewSessionStore里go s.gcLoop()启动后台 goroutine防止过期 Session 占内存。即使用户不退出24 小时后Get也会判过期。5.6 RefreshUser扩展func (s *SessionStore) RefreshUser(r *http.Request, user *models.User) { // 更新 Session 内 User 指针并续期 }改昵称等场景可刷新 Session当前项目注册后未单独调用了解即可。六、前端credentials 与跳转逻辑static/js/app.jsasync function api(url, options {}) { const res await fetch(url, { headers: { Content-Type: application/json, ...(options.headers || {}) }, credentials: same-origin, // 关键同源请求携带 Cookie ...options, }); ... } async function doLogin(e) { e.preventDefault(); const fd new FormData(e.target); try { const user await api(/api/login, { method: POST, body: JSON.stringify({ username: fd.get(username), password: fd.get(password) }), }); location.href user.role 1 ? /admin : /; } catch (err) { alert(err.message); } return false; } async function logout() { await api(/api/logout, { method: POST, body: {} }); location.href /; }credentials: same-origin含义默许fetch不带 Cookie设为same-origin后对/api/login的响应Set-Cookie会被保存后续/api/*和页面请求都会带上ttms_sid。登录后按role分流role 1→ 管理后台role 0→ 顾客首页。与models.RoleAdmin、RoleCustomer常量一致。七、Session 存什么为什么不存密码Session 存完整*models.User包括Password、Salt字段——只在服务器内存。每次Get直接返回该指针handler 可立刻用u.ID、u.IsAdmin()不必再查库。权衡优点缺点快无 DB 压力改用户 role/昵称后 Session 内是旧数据直到重新登录实现简单服务重启 Session 全丢用户要重新登录课设够用多机部署不能共享内存 map生产环境常用Redis Session或JWT本项目注释写明「内存 Session适合课程项目」。八、与页面/API 的配合场景是否必须登录怎么判断首页/否currentUser可为 nil仍显示片单选座/seats/是requireLogin→ 302/login下单 API是currentUser nil→ JSON Fail顶栏昵称可选有 User 就显示登录态贯穿所有带Cookie的请求未登录访问公开页不会报错只是User为空。九、调试技巧Chrome DevTools → Application → Cookies看是否有ttms_sid。Network → 任意请求 → Request HeadersCookie: ttms_sid...。重启 Go 程序内存 Session 清空Cookie 仍在但Get返回 nil —— 表现为「Cookie 在但显示未登录」。curl 模拟curl -c jar.txt -X POST http://localhost:8080/api/login -H Content-Type: application/json -d {\username\:\user\,\password\:\user123\} curl -b jar.txt http://localhost:8080/api/me十、小结登录验证密码后SessionStore.Create生成随机 ID内存 map Set-Cookiettms_sid。之后Get(r)读 Cookie 查 map得到*User过期或无效返回nil。退出Destroy删 map 并MaxAge-1清 Cookie。前端fetch必须credentials: same-origin才能收发 Cookie。内存 Session 24h TTL 10 分钟 GC重启丢失——课设清晰上线需换方案。一句话「登录成功后服务器发一张『(sessionId) 号手环』给浏览器 Cookie以后每次请求亮手环内存里查对应用户。」下一篇03-顾客与管理员权限.md——role字段如何驱动页面与 API 的requireLogin/requireAdmin/apiUser。

相关新闻

​​​​​​功能详解 01 · 用户注册:从表单到数据库一行

​​​​​​功能详解 01 · 用户注册:从表单到数据库一行

🥰个人主页:会编程的土豆(欢迎来访) 💎作者简介:后端学习者 ❄️个人专栏:数据结构与算法,数据库,leetcode ✨那些你一个人走过的夜路,终将化作照亮未来的光 …

2026/9/3 7:07:44 阅读更多 →
目前 Claude / GPT 的订阅建议与反代避坑

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

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

2026/8/25 4:38:33 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-@Extend 的使用

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

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

2026/8/30 17:41:45 阅读更多 →

最新新闻

MATLAB点云三维重建:从离散数据到连续模型的完整实践指南

MATLAB点云三维重建:从离散数据到连续模型的完整实践指南

简介:本资源是一套面向计算机视觉与三维建模初学者及科研实践者的MATLAB点云三维重建完整实现方案,聚焦激光雷达或结构光获取的离散点云数据,解决从原始数据导入、滤波分割、表面重建到网格可视化的一站式技术落地问题,适用于机器…

2026/9/3 8:33:02 阅读更多 →
InternSVG: Towards Unified SVG Tasks with Multimodal Large Language Models

InternSVG: Towards Unified SVG Tasks with Multimodal Large Language Models

该文章提出了InternSVG系列,通过整合数据集、基准测试和模型,实现了SVG理解、编辑和生成的统一建模,显著提升了SVG相关任务的性能。 一、文章主要内容 研究背景:当前SVG建模存在数据集碎片化、方法跨任务迁移性差、处理结构复杂性难的问题,现有数据集多针对单一任务,规模…

2026/9/3 8:33:02 阅读更多 →
Python+Open3D+PyQt点云桌面应用开发:从算法到GUI的完整实践

Python+Open3D+PyQt点云桌面应用开发:从算法到GUI的完整实践

简介:本资源是一套面向Python三维可视化开发者的实战型点云处理方案,聚焦PyQt5 GUI框架与Open3D库的深度集成,解决点云数据在桌面应用中嵌入式交互显示的核心难题,适用于计算机视觉、自动驾驶点云分析、三维重建等领域的初/中级开…

2026/9/3 8:33:02 阅读更多 →
Are Large Language Models Effective Knowledge Graph Constructors?

Are Large Language Models Effective Knowledge Graph Constructors?

该文章提出了一种基于大型语言模型(LLMs)的分层知识图谱构建框架,通过多阶段信息提取解决现有方法的局限性,并发布了相关数据集,同时从结构和语义维度全面评估了LLMs在知识图谱构建中的表现。 一、文章主要内容总结 研究背景与问题 知识图谱(KGs)对知识密集型任务至关重…

2026/9/3 8:33:02 阅读更多 →
基于STM32F103C8T6的智能家居控制系统:从原理到实践的毕业设计指南

基于STM32F103C8T6的智能家居控制系统:从原理到实践的毕业设计指南

简介:本资源是一套基于STM32F103C8T6芯片实现的完整智能家居控制系统毕业设计项目,面向计算机、物联网、自动化等专业的本科生,适用于毕业设计、课程设计及期末大作业等实践场景,有效解决嵌入式系统开发中软硬件协同、传感器数据采…

2026/9/3 8:33:02 阅读更多 →
CSDN技术博客选题指南:远离非技术内容,聚焦实战主题

CSDN技术博客选题指南:远离非技术内容,聚焦实战主题

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

2026/9/3 8:32:01 阅读更多 →

日新闻

AI智能体辅助JS逆向:从V8环境搭建到补环境实战

AI智能体辅助JS逆向:从V8环境搭建到补环境实战

先别急着点开,这不是劝退文,而是想讲清楚一件事:用 AI 做逆向值不值得学?如果要用,怎么搭一套“V8 环境 AI 智能体”来提升效率。最近逆向圈、爬虫圈都在聊 AI Agent、AST 工程逆向、JS 逆向这些词,很多新手…

2026/9/3 0:00:29 阅读更多 →
安卓设备通过修改机型信息解锁游戏高帧率:原理、操作与风险指南

安卓设备通过修改机型信息解锁游戏高帧率:原理、操作与风险指南

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

2026/9/3 0:00:29 阅读更多 →
ARM版OpenJDK 11安装部署全攻略:下载、配置与避坑指南

ARM版OpenJDK 11安装部署全攻略:下载、配置与避坑指南

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

2026/9/3 0:00:29 阅读更多 →

周新闻

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

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

每年校招季我都会接触不少准备数据库方向笔试的同学,看到最多的状态就是:简历上写着“熟悉 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 阅读更多 →