Files
hotime/docs/QuickStart_快速上手.md
hoteas f53d406110 refactor(application): 更新 Cache-Control 逻辑与文档说明
- 简化 application.go 中的 Cache-Control 设置,固定为 public,移除 logLevel 对其的控制。
- 更新 var.go 中 logLevel 的说明,明确其不再影响静态资源的 Cache-Control。
- 修改 QuickStart 文档,反映 logLevel 的新行为与静态资源缓存策略。
2026-07-15 23:02:12 +08:00

577 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HoTime 快速上手指南
5 分钟入门 HoTime 框架。完整文档索引见 [README.md](README.md)。
## 安装
```bash
go get code.hoteas.com/golang/hotime
```
## 最小示例
```go
package main
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
func main() {
appIns := Init("config/config.json")
appIns.Run(Router{
"app": {
"test": {
"hello": func(that *Context) {
that.Display(0, Map{"message": "Hello World"})
},
},
},
})
}
```
访问: `http://localhost:8081/app/test/hello`
## 配置文件
创建 `config/config.json`:
```json
{
"port": "8081",
"mode": 2,
"sessionName": "HOTIME",
"tpt": "tpt",
"defFile": ["index.html", "index.htm"],
"db": {
"mysql": {
"host": "localhost",
"port": "3306",
"name": "your_database",
"user": "root",
"password": "your_password",
"prefix": ""
}
},
"cache": {
"memory": {
"db": true,
"session": true,
"timeout": 7200
}
}
}
```
### 配置项说明
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `port` | 80 | HTTP 服务端口,0 为不启用 |
| `tlsPort` | - | HTTPS 端口,需配合 tlsCert/tlsKey |
| `tlsCert` | - | HTTPS 证书路径 |
| `tlsKey` | - | HTTPS 密钥路径 |
| `mode` | 0 | 0=生产, 1=测试, 2=开发(输出SQL) |
| `tpt` | tpt | 静态文件目录 |
| `sessionName` | HOTIME | Session Cookie 名称 |
| `modeRouterStrict` | false | 路由大小写敏感,false=忽略大小写 |
| `crossDomain` | - | 跨域设置,空=不开启,auto=智能开启,或指定域名 |
| `logFile` | - | 日志文件路径,如 `logs/20060102.txt` |
| `logLevel` | 1 | 日志等级:0=仅错误,>=1=全部;同时控制 SQL 日志。**不**控制静态 `Cache-Control`(静态固定 `public` |
| `webConnectLogShow` | true | 是否显示访问日志 |
| `defFile` | ["index.html"] | 目录默认访问文件 |
### 数据库配置
支持的数据库类型:`mysql``sqlite``postgres`PostgreSQL)、`dm`(达梦 DM8)。
```json
{
"db": {
"mysql": {
"host": "127.0.0.1",
"port": "3306",
"name": "database_name",
"user": "root",
"password": "password",
"prefix": "app_",
"slave": {
"host": "127.0.0.1",
"port": "3306",
"name": "database_name",
"user": "root",
"password": "password"
}
},
"sqlite": {
"path": "config/data.db",
"prefix": ""
},
"dm": {
"host": "127.0.0.1",
"port": "5236",
"name": "TEST",
"user": "SYSDBA",
"password": "your_password",
"prefix": ""
}
}
}
```
> MySQL 配置 `slave` 项即启用主从读写分离
> 达梦 DM8 的 `name` 字段对应 schema(搜索路径),驱动已内置于 `vendor/gitee.com/chunanyong/dm`,无需额外安装
### 缓存配置
```json
{
"cache": {
"memory": {
"db": true,
"session": true,
"timeout": 7200
},
"redis": {
"host": "127.0.0.1",
"port": 6379,
"password": "",
"db": true,
"session": true,
"timeout": 1296000
},
"db": {
"db": true,
"session": true,
"timeout": 2592000,
"history": false,
"mode": "compatible"
}
}
}
```
缓存优先级: **Memory > Redis > DB**,自动穿透与回填
#### DB 缓存配置说明
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `db` | false | 是否缓存数据库查询 |
| `session` | true | 是否缓存 Session |
| `timeout` | 2592000 | 过期时间(秒) |
| `history` | false | 是否记录缓存历史,开启后每次新增/修改缓存都会记录到历史表 |
| `mode` | compatible | 缓存表模式,见下表 |
**缓存表模式 (mode)**
| 模式 | 说明 |
|------|------|
| `compatible` | **默认**。兼容模式:写新表,新表无数据时回退读老表;写入时自动删除老表同 key 记录;删除时同时删两表。适合从老版本平滑升级,老数据自然过期消亡 |
| `new` | 只使用新表 `hotime_cache`,启动时自动迁移老表 `cached` 数据。老表保留由人工删除,不再被读写 |
> **升级建议**:从老版本升级时,建议先使用 `compatible` 模式运行一段时间(让老数据自然过期),确认无问题后再切换到 `new` 模式
> 从老版本升级时,建议使用 `compatible` 模式平滑过渡,待老表数据消亡后切换到 `new` 模式
### 错误码配置
```json
{
"error": {
"1": "内部系统异常",
"2": "访问权限异常",
"3": "请求参数异常",
"4": "数据处理异常",
"5": "数据结果异常"
}
}
```
> 自定义错误码建议从 10 开始
## 路由系统
HoTime 使用三层路由结构:`模块/控制器/方法`
```go
appIns.Run(Router{
"模块名": {
"控制器名": {
"方法名": func(that *Context) {
// 处理逻辑
},
},
},
})
```
### 路由路径
```go
// 获取路由信息
module := that.RouterString[0] // 模块
controller := that.RouterString[1] // 控制器
action := that.RouterString[2] // 方法
// 完整请求路径
fullPath := that.HandlerStr // 如 /app/user/login
```
## 请求参数获取
### 新版推荐方法(支持链式调用)
```go
// 获取 URL 查询参数 (?id=1)
id := that.ReqParam("id").ToInt()
name := that.ReqParam("name").ToStr()
// 获取表单参数 (POST form-data / x-www-form-urlencoded)
username := that.ReqForm("username").ToStr()
age := that.ReqForm("age").ToInt()
// 获取 JSON Body 参数 (POST application/json)
data := that.ReqJson("data").ToMap()
items := that.ReqJson("items").ToSlice()
// 统一获取(自动判断来源,优先级: JSON > Form > URL
userId := that.ReqData("user_id").ToInt()
status := that.ReqData("status").ToStr()
```
### 类型转换方法
```go
obj := that.ReqData("key")
obj.ToStr() // 转字符串
obj.ToInt() // 转 int
obj.ToInt64() // 转 int64
obj.ToFloat64() // 转 float64
obj.ToBool() // 转 bool
obj.ToMap() // 转 Map
obj.ToSlice() // 转 Slice
obj.Data // 获取原始值(interface{}
```
### 文件上传
```go
// 单文件上传
file, header, err := that.ReqFile("avatar")
if err == nil {
defer file.Close()
// header.Filename - 文件名
// header.Size - 文件大小
}
// 多文件上传(批量)
files, err := that.ReqFiles("images")
if err == nil {
for _, fh := range files {
file, _ := fh.Open()
defer file.Close()
// 处理每个文件
}
}
```
### 传统方法(兼容)
```go
// GET/POST 参数
name := that.Req.FormValue("name")
// URL 参数
id := that.Req.URL.Query().Get("id")
// 请求头
token := that.Req.Header.Get("Authorization")
```
## 响应数据
### Display 方法
```go
// 成功响应 (status=0)
that.Display(0, Map{"user": user, "token": token})
// 输出: {"status":0, "result":{"user":..., "token":...}}
// 错误响应 (status>0)
that.Display(1, "系统内部错误")
// 输出: {"status":1, "result":{"type":"内部系统异常", "msg":"系统内部错误"}, "error":{...}}
that.Display(2, "请先登录")
// 输出: {"status":2, "result":{"type":"访问权限异常", "msg":"请先登录"}, "error":{...}}
that.Display(3, "参数不能为空")
// 输出: {"status":3, "result":{"type":"请求参数异常", "msg":"参数不能为空"}, "error":{...}}
```
### 错误码含义
| 错误码 | 类型 | 使用场景 |
|--------|------|----------|
| 0 | 成功 | 请求成功 |
| 1 | 内部系统异常 | 环境配置、文件权限等基础运行环境错误 |
| 2 | 访问权限异常 | 未登录或登录异常 |
| 3 | 请求参数异常 | 参数不足、类型错误等 |
| 4 | 数据处理异常 | 数据库操作或第三方请求返回异常 |
| 5 | 数据结果异常 | 无法返回要求的格式 |
### 自定义响应
```go
// 自定义 Header
that.Resp.Header().Set("Content-Type", "application/json")
// 直接写入
that.Resp.Write([]byte("raw data"))
// 自定义响应函数
that.RespFunc = func() {
// 自定义响应逻辑
}
```
## 中间件
```go
// 全局中间件(请求拦截)
appIns.SetConnectListener(func(that *Context) bool {
// 放行登录接口
if len(that.RouterString) >= 3 && that.RouterString[2] == "login" {
return false
}
// 检查登录状态
if that.Session("user_id").Data == nil {
that.Display(2, "请先登录")
return true // 返回 true 终止请求
}
return false // 返回 false 继续处理
})
```
## Session 与缓存
```go
// Session 操作
that.Session("user_id", 123) // 设置
userId := that.Session("user_id") // 获取 *Obj
that.Session("user_id", nil) // 删除
// 链式获取
id := that.Session("user_id").ToInt64()
name := that.Session("username").ToStr()
// 通用缓存
that.Cache("key", "value") // 设置
data := that.Cache("key") // 获取
that.Cache("key", nil) // 删除
```
### 批量操作(性能优化)
当需要同时操作多个 Session 字段时,使用批量操作可显著提升性能:
```go
// SessionsSet - 批量设置(N个字段只触发1次数据库写入)
that.SessionsSet(Map{
"user_id": userId,
"username": "张三",
"login_time": time.Now().Unix(),
"role": "admin",
})
// SessionsGet - 批量获取(1次调用获取多个字段)
result := that.SessionsGet("user_id", "username", "role")
// result = Map{"user_id": 123, "username": "张三", "role": "admin"}
userId := ObjToInt64(result["user_id"], nil)
username := ObjToStr(result["username"])
// SessionsDelete - 批量删除(N个字段只触发1次数据库写入)
that.SessionsDelete("token", "temp_code", "verify_expire")
```
**性能对比**
| 操作方式 | 设置10个字段 | 数据库写入次数 |
|----------|-------------|---------------|
| 逐个调用 `Session()` | 10次调用 | **10次** |
| 使用 `SessionsSet()` | 1次调用 | **1次** |
| 操作方式 | 获取10个字段 | 缓存查询次数 |
|----------|-------------|--------------|
| 逐个调用 `Session()` | 10次调用 | **10次** |
| 使用 `SessionsGet()` | 1次调用 | **1次** |
> 💡 **最佳实践**:当一次性操作 3 个以上字段时,建议使用批量操作
三级缓存自动运作:**Memory → Redis → Database**
## 数据库操作(简要)
```go
users := that.Db.Select("user", "*", Map{"status": 1})
user := that.Db.Get("user", "*", Map{"id": 1})
id := that.Db.Insert("user", Map{"name": "test", "age": 18})
that.Db.Update("user", Map{"name": "new"}, Map{"id": 1})
that.Db.Delete("user", Map{"id": 1})
users = that.Db.Table("user").
Where("status", 1).And("age[>]", 18).
Order("id DESC").Page(1, 10).Select("*")
```
条件运算符(`[>]` / `[~]` / IN 等)、事务、缓存与方言差异见:
- 教程:[HoTimeDB 使用说明](HoTimeDB_使用说明.md)
- 速查:[HoTimeDB API 参考](HoTimeDB_API参考.md)
## 日志:业务 Log 表 vs Seq
| 能力 | 用途 | 文档 |
|------|------|------|
| `that.Log = Map{...}` | 写入业务 `logs` 表(操作审计) | 见下方示例 |
| `seqUrl` + zerolog | 结构化日志推送到 Seq 集中搜索 | [Seq 日志集成](Seq_日志集成.md) |
```go
// 业务操作日志(自动插入 logs 表;框架会补 time、admin_id/user_id、ip 等)
that.Log = Map{
"type": "login",
"action": "用户登录",
"data": Map{"phone": phone},
}
```
## 扩展功能
| 功能 | 路径 | 说明 |
|------|------|------|
| 微信支付/公众号/小程序 | `dri/wechat/` | 微信全套 SDK |
| 阿里云服务 | `dri/aliyun/` | 企业认证等 |
| 腾讯云服务 | `dri/tencent/` | 企业认证等 |
| 文件上传 | `dri/upload/` | 文件上传处理 |
| 文件下载 | `dri/download/` | 文件下载处理 |
| MongoDB | `dri/mongodb/` | MongoDB 驱动 |
| RSA 加解密 | `dri/rsa/` | RSA 加解密工具 |
## 完整示例
```go
package main
import (
"time"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
func main() {
appIns := Init("config/config.json")
// 登录检查中间件
appIns.SetConnectListener(func(that *Context) bool {
// 放行登录接口
if len(that.RouterString) >= 3 && that.RouterString[2] == "login" {
return false
}
if that.Session("user_id").Data == nil {
that.Display(2, "请先登录")
return true
}
return false
})
appIns.Run(Router{
"api": {
"user": {
"login": func(that *Context) {
phone := that.ReqData("phone").ToStr()
password := that.ReqData("password").ToStr()
if phone == "" || password == "" {
that.Display(3, "手机号和密码不能为空")
return
}
user := that.Db.Get("user", "*", Map{
"phone": phone,
"password": Md5(password),
})
if user == nil {
that.Display(3, "账号或密码错误")
return
}
// 使用批量设置,一次写入多个字段
that.SessionsSet(Map{
"user_id": user.GetInt64("id"),
"username": user.GetString("name"),
"login_time": time.Now().Unix(),
})
that.Display(0, Map{"user": user})
},
"info": func(that *Context) {
// 使用批量获取,一次读取多个字段
sess := that.SessionsGet("user_id", "username", "login_time")
userId := ObjToInt64(sess["user_id"], nil)
user := that.Db.Get("user", "*", Map{"id": userId})
that.Display(0, Map{
"user": user,
"login_time": sess["login_time"],
})
},
"list": func(that *Context) {
page := that.ReqData("page").ToInt()
if page == 0 {
page = 1
}
users := that.Db.Table("user").
Where("status", 1).
Order("id DESC").
Page(page, 10).
Select("id,name,phone,created_at")
total := that.Db.Count("user", Map{"status": 1})
that.Display(0, Map{
"list": users,
"total": total,
"page": page,
})
},
"logout": func(that *Context) {
// 使用批量删除,一次清除多个字段
that.SessionsDelete("user_id", "username", "login_time")
that.Display(0, "退出成功")
},
},
},
})
}
```
---
**下一步**
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) — ORM 教程
- [HoTimeDB API 参考](HoTimeDB_API参考.md) — API 速查
- [Common 工具类](Common_工具类.md) — Map/Slice/Obj
- [API 测试框架](Testing_API测试框架.md) — 接口测试(优先 `-run` 单测)
- [文档索引](README.md) — 完整阅读路径