6b689f3a1b
- 在 .gitignore 中添加调试日志文件的忽略规则,避免不必要的调试信息被提交 - 修改 application.go 中的 stdout 重定向逻辑,使用 log.CaptureStream 以支持更灵活的日志捕获 - 更新 README 文档,增加对日志重定向功能的说明
16 KiB
16 KiB
HoTime 快速上手指南
5 分钟入门 HoTime 框架。完整文档索引见 README.md。
安装
go get code.hoteas.com/golang/hotime
最小示例
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:
{
"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 |
0 | 日志等级,0=关闭,1=打印 |
webConnectLogShow |
true | 是否显示访问日志 |
defFile |
["index.html"] | 目录默认访问文件 |
数据库配置
支持的数据库类型:mysql、sqlite、postgres(PostgreSQL)、dm(达梦 DM8)。
{
"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,无需额外安装
缓存配置
{
"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模式
错误码配置
{
"error": {
"1": "内部系统异常",
"2": "访问权限异常",
"3": "请求参数异常",
"4": "数据处理异常",
"5": "数据结果异常"
}
}
自定义错误码建议从 10 开始
路由系统
HoTime 使用三层路由结构:模块/控制器/方法
appIns.Run(Router{
"模块名": {
"控制器名": {
"方法名": func(that *Context) {
// 处理逻辑
},
},
},
})
路由路径
// 获取路由信息
module := that.RouterString[0] // 模块
controller := that.RouterString[1] // 控制器
action := that.RouterString[2] // 方法
// 完整请求路径
fullPath := that.HandlerStr // 如 /app/user/login
请求参数获取
新版推荐方法(支持链式调用)
// 获取 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()
类型转换方法
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{})
文件上传
// 单文件上传
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()
// 处理每个文件
}
}
传统方法(兼容)
// GET/POST 参数
name := that.Req.FormValue("name")
// URL 参数
id := that.Req.URL.Query().Get("id")
// 请求头
token := that.Req.Header.Get("Authorization")
响应数据
Display 方法
// 成功响应 (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 | 数据结果异常 | 无法返回要求的格式 |
自定义响应
// 自定义 Header
that.Resp.Header().Set("Content-Type", "application/json")
// 直接写入
that.Resp.Write([]byte("raw data"))
// 自定义响应函数
that.RespFunc = func() {
// 自定义响应逻辑
}
中间件
// 全局中间件(请求拦截)
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 与缓存
// 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 字段时,使用批量操作可显著提升性能:
// 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
数据库操作(简要)
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 API 参考
日志:业务 Log 表 vs Seq
| 能力 | 用途 | 文档 |
|---|---|---|
that.Log = Map{...} |
写入业务 logs 表(操作审计) |
见下方示例 |
seqUrl + zerolog |
结构化日志推送到 Seq 集中搜索 | Seq 日志集成 |
// 业务操作日志(自动插入 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 加解密工具 |
完整示例
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 使用说明 — ORM 教程
- HoTimeDB API 参考 — API 速查
- Common 工具类 — Map/Slice/Obj
- API 测试框架 — 接口测试(优先
-run单测) - 文档索引 — 完整阅读路径