From 8dac2aff6660a58b2edf8eeceaeeea957c77cfaa Mon Sep 17 00:00:00 2001 From: hoteas <925970985@qq.com> Date: Thu, 22 Jan 2026 22:13:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(guides):=20=E5=AE=8C=E5=96=84=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E6=96=87=E6=A1=A3=E5=B9=B6=E6=96=B0=E5=A2=9E=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=94=9F=E6=88=90=E5=99=A8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加代码生成器完整使用说明文档,包含配置规则和自定义规则 - 新增 Common 工具类使用说明,介绍 Map/Slice/Obj 类型及转换函数 - 更新 QUICKSTART 文档中的配置项说明和数据库配置示例 - 完善请求参数获取方法,添加新版链式调用推荐用法 - 更新响应数据处理说明,包含错误码含义和自定义响应方法 - 优化中间件和 Session 操作的代码示例 - 修正路由路径参数获取的安全检查逻辑 - 更新 README 添加新文档链接索引 --- README.md | 2 + docs/CodeGen_使用说明.md | 468 ++++++++++++++++++++++++++++++++ docs/Common_工具类使用说明.md | 484 ++++++++++++++++++++++++++++++++++ docs/QUICKSTART.md | 287 +++++++++++++++++--- 4 files changed, 1211 insertions(+), 30 deletions(-) create mode 100644 docs/CodeGen_使用说明.md create mode 100644 docs/Common_工具类使用说明.md diff --git a/README.md b/README.md index ef67bfb..b03d781 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ | [快速上手指南](docs/QUICKSTART.md) | 5 分钟入门,安装配置、路由、中间件、基础数据库操作 | | [HoTimeDB 使用说明](docs/HoTimeDB_使用说明.md) | 完整数据库 ORM 教程 | | [HoTimeDB API 参考](docs/HoTimeDB_API参考.md) | 数据库 API 速查手册 | +| [Common 工具类](docs/Common_工具类使用说明.md) | Map/Slice/Obj 类型、类型转换、工具函数 | +| [代码生成器](docs/CodeGen_使用说明.md) | 自动 CRUD 代码生成、配置规则 | ## 安装 diff --git a/docs/CodeGen_使用说明.md b/docs/CodeGen_使用说明.md new file mode 100644 index 0000000..22e61d2 --- /dev/null +++ b/docs/CodeGen_使用说明.md @@ -0,0 +1,468 @@ +# HoTime 代码生成器使用说明 + +`code` 包提供了 HoTime 框架的自动代码生成功能,能够根据数据库表结构自动生成 CRUD 接口代码和配置文件。 + +## 目录 + +- [功能概述](#功能概述) +- [配置说明](#配置说明) +- [使用方法](#使用方法) +- [生成规则](#生成规则) +- [自定义规则](#自定义规则) +- [生成的代码结构](#生成的代码结构) + +--- + +## 功能概述 + +代码生成器可以: + +1. **自动读取数据库表结构** - 支持 MySQL 和 SQLite +2. **生成 CRUD 接口** - 增删改查、搜索、分页 +3. **生成配置文件** - 表字段配置、菜单配置、权限配置 +4. **智能字段识别** - 根据字段名自动识别类型和权限 +5. **支持表关联** - 自动识别外键关系 + +--- + +## 配置说明 + +在 `config.json` 中配置代码生成: + +```json +{ + "codeConfig": [ + { + "table": "admin", + "config": "config/admin.json", + "configDB": "config/adminDB.json", + "rule": "config/rule.json", + "name": "", + "mode": 0 + } + ] +} +``` + +### 配置项说明 + +| 配置项 | 必须 | 说明 | +|--------|------|------| +| `table` | ✅ | 用户表名,用于权限控制的基准表 | +| `config` | ✅ | 接口描述配置文件路径 | +| `configDB` | ❌ | 数据库结构配置输出路径,有则每次自动生成 | +| `rule` | ❌ | 字段规则配置文件,无则使用默认规则 | +| `name` | ❌ | 生成代码的包名和目录名,空则使用内嵌模式 | +| `mode` | ❌ | 0=内嵌代码模式,1=生成代码模式 | + +### 运行模式 + +- **mode=0(内嵌模式)**:不生成独立代码文件,使用框架内置的通用控制器 +- **mode=1(生成模式)**:为每张表生成独立的 Go 控制器文件 + +--- + +## 使用方法 + +### 1. 基础配置 + +```json +{ + "mode": 2, + "codeConfig": [ + { + "table": "admin", + "config": "config/admin.json", + "rule": "config/rule.json", + "mode": 0 + } + ] +} +``` + +### 2. 启动应用 + +```go +package main + +import ( + . "code.hoteas.com/golang/hotime" + . "code.hoteas.com/golang/hotime/common" +) + +func main() { + app := Init("config/config.json") + + // 代码生成器在 Init 时自动执行 + // 会读取数据库结构并生成配置 + + app.Run(Router{ + // 路由配置 + }) +} +``` + +### 3. 开发模式 + +在 `config.json` 中设置 `"mode": 2`(开发模式)时: + +- 自动读取数据库表结构 +- 自动生成/更新配置文件 +- 自动生成代码(如果 codeConfig.mode=1) + +--- + +## 生成规则 + +### 默认字段规则 + +代码生成器内置了一套默认的字段识别规则: + +| 字段名 | 列表显示 | 新增 | 编辑 | 详情 | 类型 | +|--------|----------|------|------|------|------| +| `id` | ✅ | ❌ | ❌ | ✅ | number | +| `name` | ✅ | ✅ | ✅ | ✅ | text | +| `status` | ✅ | ✅ | ✅ | ✅ | select | +| `create_time` | ❌ | ❌ | ❌ | ✅ | time | +| `modify_time` | ✅ | ❌ | ❌ | ✅ | time | +| `password` | ❌ | ✅ | ✅ | ❌ | password | +| `image/img/avatar` | ❌ | ✅ | ✅ | ✅ | image | +| `file` | ❌ | ✅ | ✅ | ✅ | file | +| `content/info` | ❌ | ✅ | ✅ | ✅ | textArea | +| `parent_id` | ✅ | ✅ | ✅ | ✅ | number | +| `parent_ids/index` | ❌ | ❌ | ❌ | ❌ | index | +| `delete` | ❌ | ❌ | ❌ | ❌ | - | + +### 数据类型映射 + +数据库字段类型自动映射: + +| 数据库类型 | 生成类型 | +|------------|----------| +| `int`, `integer`, `float`, `double`, `decimal` | number | +| `char`, `varchar`, `text`, `blob` | text | +| `date`, `datetime`, `time`, `timestamp`, `year` | time | + +### 字段备注解析 + +支持从数据库字段备注中提取信息: + +```sql +-- 字段备注格式: 标签名:选项1-名称1,选项2-名称2 {提示信息} +-- 例如: +status TINYINT COMMENT '状态:0-禁用,1-启用 {用户账号状态}' +``` + +生成的配置: + +```json +{ + "name": "status", + "label": "状态", + "type": "select", + "ps": "用户账号状态", + "options": [ + {"name": "禁用", "value": "0"}, + {"name": "启用", "value": "1"} + ] +} +``` + +--- + +## 自定义规则 + +### rule.json 配置 + +创建 `config/rule.json` 自定义字段规则: + +```json +[ + { + "name": "id", + "list": true, + "add": false, + "edit": false, + "info": true, + "must": false, + "strict": true, + "type": "" + }, + { + "name": "status", + "list": true, + "add": true, + "edit": true, + "info": true, + "must": false, + "strict": false, + "type": "select" + }, + { + "name": "user.special_field", + "list": true, + "add": true, + "edit": true, + "info": true, + "type": "text", + "strict": true + } +] +``` + +### 规则字段说明 + +| 字段 | 说明 | +|------|------| +| `name` | 字段名,支持 `表名.字段名` 格式精确匹配 | +| `list` | 是否在列表中显示 | +| `add` | 是否在新增表单中显示 | +| `edit` | 是否在编辑表单中显示 | +| `info` | 是否在详情中显示 | +| `must` | 是否必填 | +| `strict` | 是否严格匹配字段名(false 则模糊匹配) | +| `type` | 字段类型(覆盖自动识别) | + +### 字段类型 + +| 类型 | 说明 | +|------|------| +| `text` | 普通文本输入 | +| `textArea` | 多行文本 | +| `number` | 数字输入 | +| `select` | 下拉选择 | +| `time` | 时间选择器 | +| `unixTime` | Unix 时间戳 | +| `image` | 图片上传 | +| `file` | 文件上传 | +| `password` | 密码输入 | +| `money` | 金额(带格式化) | +| `index` | 索引字段(不显示) | +| `tree` | 树形选择 | +| `form` | 表单配置 | +| `auth` | 权限配置 | + +--- + +## 生成的代码结构 + +### 内嵌模式 (mode=0) + +不生成代码文件,使用框架内置控制器,只生成配置文件: + +``` +config/ +├── admin.json # 接口配置 +├── adminDB.json # 数据库结构配置(可选) +└── rule.json # 字段规则 +``` + +### 生成模式 (mode=1) + +生成独立的控制器代码: + +``` +admin/ # 生成的包目录 +├── init.go # 包初始化和路由注册 +├── user.go # user 表控制器 +├── role.go # role 表控制器 +└── ... # 其他表控制器 +``` + +### 生成的控制器结构 + +```go +package admin + +var userCtr = Ctr{ + "info": func(that *Context) { + // 查询单条记录 + }, + "add": func(that *Context) { + // 新增记录 + }, + "update": func(that *Context) { + // 更新记录 + }, + "remove": func(that *Context) { + // 删除记录 + }, + "search": func(that *Context) { + // 搜索列表(分页) + }, +} +``` + +--- + +## 配置文件结构 + +### admin.json 示例 + +```json +{ + "name": "admin", + "label": "管理平台", + "menus": [ + { + "label": "系统管理", + "name": "sys", + "icon": "Setting", + "menus": [ + { + "label": "用户管理", + "table": "user", + "auth": ["show", "add", "delete", "edit", "info", "download"] + }, + { + "label": "角色管理", + "table": "role", + "auth": ["show", "add", "delete", "edit", "info"] + } + ] + } + ], + "tables": { + "user": { + "label": "用户", + "table": "user", + "auth": ["show", "add", "delete", "edit", "info", "download"], + "columns": [ + {"name": "id", "type": "number", "label": "ID"}, + {"name": "name", "type": "text", "label": "用户名"}, + {"name": "status", "type": "select", "label": "状态", + "options": [{"name": "禁用", "value": "0"}, {"name": "启用", "value": "1"}]} + ], + "search": [ + {"type": "search", "name": "keyword", "label": "请输入关键词"}, + {"type": "search", "name": "daterange", "label": "时间段"} + ] + } + } +} +``` + +--- + +## 外键关联 + +### 自动识别 + +代码生成器会自动识别 `_id` 结尾的字段作为外键: + +```sql +-- user 表 +CREATE TABLE user ( + id INT PRIMARY KEY, + name VARCHAR(50), + role_id INT, -- 自动关联 role 表 + org_id INT -- 自动关联 org 表 +); +``` + +生成的配置会包含 `link` 和 `value` 字段: + +```json +{ + "name": "role_id", + "type": "number", + "label": "角色", + "link": "role", + "value": "name" +} +``` + +### 树形结构 + +`parent_id` 字段会被识别为树形结构的父级关联: + +```json +{ + "name": "parent_id", + "type": "number", + "label": "上级", + "link": "org", + "value": "name" +} +``` + +--- + +## 权限控制 + +### 数据权限 + +配置 `flow` 实现数据权限控制: + +```json +{ + "flow": { + "order": { + "table": "order", + "stop": false, + "sql": { + "user_id": "id" + } + } + } +} +``` + +- `stop`: 是否禁止修改该表 +- `sql`: 数据过滤条件,`user_id = 当前用户.id` + +### 操作权限 + +每张表可配置的权限: + +| 权限 | 说明 | +|------|------| +| `show` | 查看列表 | +| `add` | 新增 | +| `edit` | 编辑 | +| `delete` | 删除 | +| `info` | 查看详情 | +| `download` | 下载导出 | + +--- + +## 最佳实践 + +### 1. 开发流程 + +1. 设置 `config.json` 中 `mode: 2`(开发模式) +2. 设计数据库表结构,添加字段备注 +3. 启动应用,自动生成配置 +4. 检查生成的配置文件,按需调整 +5. 生产环境改为 `mode: 0` + +### 2. 字段命名规范 + +```sql +-- 推荐的命名方式 +id -- 主键 +name -- 名称 +status -- 状态(自动识别为 select) +create_time -- 创建时间 +modify_time -- 修改时间 +xxx_id -- 外键关联 +parent_id -- 树形结构父级 +avatar -- 头像(自动识别为 image) +content -- 内容(自动识别为 textArea) +``` + +### 3. 自定义扩展 + +如果默认规则不满足需求,可以: + +1. 修改 `rule.json` 添加自定义规则 +2. 使用 `mode=1` 生成代码后手动修改 +3. 在生成的配置文件中直接调整字段属性 + +--- + +## 相关文档 + +- [快速上手指南](QUICKSTART.md) +- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) +- [Common 工具类使用说明](Common_工具类使用说明.md) diff --git a/docs/Common_工具类使用说明.md b/docs/Common_工具类使用说明.md new file mode 100644 index 0000000..0c37afd --- /dev/null +++ b/docs/Common_工具类使用说明.md @@ -0,0 +1,484 @@ +# HoTime Common 工具类使用说明 + +`common` 包提供了 HoTime 框架的核心数据类型和工具函数,包括 `Map`、`Slice`、`Obj` 类型及丰富的类型转换函数。 + +## 目录 + +- [核心数据类型](#核心数据类型) + - [Map 类型](#map-类型) + - [Slice 类型](#slice-类型) + - [Obj 类型](#obj-类型) +- [类型转换函数](#类型转换函数) +- [工具函数](#工具函数) +- [错误处理](#错误处理) + +--- + +## 核心数据类型 + +### Map 类型 + +`Map` 是 `map[string]interface{}` 的别名,提供了丰富的链式调用方法。 + +```go +import . "code.hoteas.com/golang/hotime/common" + +// 创建 Map +data := Map{ + "name": "张三", + "age": 25, + "score": 98.5, + "active": true, + "tags": Slice{"Go", "Web"}, +} +``` + +#### 获取值方法 + +```go +// 获取字符串 +name := data.GetString("name") // "张三" + +// 获取整数 +age := data.GetInt("age") // 25 +age64 := data.GetInt64("age") // int64(25) + +// 获取浮点数 +score := data.GetFloat64("score") // 98.5 + +// 获取布尔值 +active := data.GetBool("active") // true + +// 获取嵌套 Map +info := data.GetMap("info") // 返回 Map 类型 + +// 获取 Slice +tags := data.GetSlice("tags") // 返回 Slice 类型 + +// 获取时间 +createTime := data.GetTime("create_time") // 返回 *time.Time + +// 获取原始值 +raw := data.Get("name") // interface{} +``` + +#### 向上取整方法 + +```go +// 向上取整获取整数 +ceilInt := data.GetCeilInt("score") // 99 +ceilInt64 := data.GetCeilInt64("score") // int64(99) +ceilFloat := data.GetCeilFloat64("score") // 99.0 +``` + +#### 操作方法 + +```go +// 添加/修改值 +data.Put("email", "test@example.com") + +// 删除值 +data.Delete("email") + +// 转换为 JSON 字符串 +jsonStr := data.ToJsonString() + +// 从 JSON 字符串解析 +data.JsonToMap(`{"key": "value"}`) +``` + +#### 有序遍历 + +```go +// 按 key 字母顺序遍历 +data.RangeSort(func(k string, v interface{}) bool { + fmt.Printf("%s: %v\n", k, v) + return false // 返回 true 则终止遍历 +}) +``` + +#### 转换为结构体 + +```go +type User struct { + Name string + Age int64 + Score float64 +} + +var user User +data.ToStruct(&user) // 传入指针,字段名首字母大写匹配 +``` + +--- + +### Slice 类型 + +`Slice` 是 `[]interface{}` 的别名,提供类似 Map 的链式调用方法。 + +```go +// 创建 Slice +list := Slice{ + Map{"id": 1, "name": "Alice"}, + Map{"id": 2, "name": "Bob"}, + "text", + 123, +} +``` + +#### 获取值方法 + +```go +// 按索引获取值(类型转换) +str := list.GetString(2) // "text" +num := list.GetInt(3) // 123 +num64 := list.GetInt64(3) // int64(123) +f := list.GetFloat64(3) // 123.0 +b := list.GetBool(3) // true (非0为true) + +// 获取嵌套类型 +item := list.GetMap(0) // Map{"id": 1, "name": "Alice"} +subList := list.GetSlice(0) // 尝试转换为 Slice + +// 获取原始值 +raw := list.Get(0) // interface{} + +// 获取时间 +t := list.GetTime(0) // *time.Time +``` + +#### 向上取整方法 + +```go +ceilInt := list.GetCeilInt(3) +ceilInt64 := list.GetCeilInt64(3) +ceilFloat := list.GetCeilFloat64(3) +``` + +#### 操作方法 + +```go +// 修改指定位置的值 +list.Put(0, "new value") + +// 转换为 JSON 字符串 +jsonStr := list.ToJsonString() +``` + +--- + +### Obj 类型 + +`Obj` 是一个通用的对象包装器,用于链式类型转换,常用于 `Context` 方法的返回值。 + +```go +type Obj struct { + Data interface{} // 原始数据 + Error // 错误信息 +} +``` + +#### 使用示例 + +```go +obj := &Obj{Data: "123"} + +// 链式类型转换 +i := obj.ToInt() // 123 +i64 := obj.ToInt64() // int64(123) +f := obj.ToFloat64() // 123.0 +s := obj.ToStr() // "123" +b := obj.ToBool() // true + +// 复杂类型转换 +m := obj.ToMap() // 尝试转换为 Map +sl := obj.ToSlice() // 尝试转换为 Slice +arr := obj.ToMapArray() // 转换为 []Map + +// 获取原始值 +raw := obj.ToObj() // interface{} + +// 获取时间 +t := obj.ToTime() // *time.Time + +// 向上取整 +ceil := obj.ToCeilInt() +ceil64 := obj.ToCeilInt64() +ceilF := obj.ToCeilFloat64() +``` + +#### 在 Context 中的应用 + +```go +func handler(that *Context) { + // ReqData 返回 *Obj,支持链式调用 + userId := that.ReqData("user_id").ToInt() + name := that.ReqData("name").ToStr() + + // Session 也返回 *Obj + adminId := that.Session("admin_id").ToInt64() +} +``` + +--- + +## 类型转换函数 + +`common` 包提供了一系列全局类型转换函数。 + +### 基础转换 + +```go +// 转字符串 +str := ObjToStr(123) // "123" +str := ObjToStr(3.14) // "3.14" +str := ObjToStr(Map{"a": 1}) // JSON 格式字符串 + +// 转整数 +i := ObjToInt("123") // 123 +i64 := ObjToInt64("123") // int64(123) + +// 转浮点数 +f := ObjToFloat64("3.14") // 3.14 + +// 转布尔 +b := ObjToBool(1) // true +b := ObjToBool(0) // false + +// 转 Map +m := ObjToMap(`{"a": 1}`) // Map{"a": 1} +m := ObjToMap(someStruct) // 结构体转 Map + +// 转 Slice +s := ObjToSlice(`[1, 2, 3]`) // Slice{1, 2, 3} + +// 转 []Map +arr := ObjToMapArray(slice) // []Map +``` + +### 向上取整转换 + +```go +// 向上取整后转整数 +ceil := ObjToCeilInt(3.2) // 4 +ceil64 := ObjToCeilInt64(3.2) // int64(4) +ceilF := ObjToCeilFloat64(3.2) // 4.0 +``` + +### 时间转换 + +```go +// 自动识别多种格式 +t := ObjToTime("2024-01-15 10:30:00") // *time.Time +t := ObjToTime("2024-01-15") // *time.Time +t := ObjToTime(1705298400) // Unix 秒 +t := ObjToTime(1705298400000) // Unix 毫秒 +t := ObjToTime(1705298400000000) // Unix 微秒 +``` + +### 字符串转换 + +```go +// 字符串转 Map +m := StrToMap(`{"key": "value"}`) + +// 字符串转 Slice +s := StrToSlice(`[1, 2, 3]`) + +// 字符串转 int +i, err := StrToInt("123") + +// 字符串数组格式转换 +jsonArr := StrArrayToJsonStr("a1,a2,a3") // "[a1,a2,a3]" +strArr := JsonStrToStrArray("[a1,a2,a3]") // ",a1,a2,a3," +``` + +### 错误处理 + +所有转换函数支持可选的错误参数: + +```go +var e Error +i := ObjToInt("abc", &e) +if e.GetError() != nil { + // 处理转换错误 +} +``` + +--- + +## 工具函数 + +### 字符串处理 + +```go +// 字符串截取(支持中文) +str := Substr("Hello世界", 0, 7) // "Hello世" +str := Substr("Hello", -2, 2) // "lo" (负数从末尾计算) + +// 首字母大写 +upper := StrFirstToUpper("hello") // "Hello" + +// 查找最后出现位置 +idx := IndexLastStr("a.b.c", ".") // 3 + +// 字符串相似度(Levenshtein 距离) +dist := StrLd("hello", "hallo", true) // 1 (忽略大小写) +``` + +### 时间处理 + +```go +// 时间转字符串 +str := Time2Str(time.Now()) // "2024-01-15 10:30:00" +str := Time2Str(time.Now(), 1) // "2024-01" +str := Time2Str(time.Now(), 2) // "2024-01-15" +str := Time2Str(time.Now(), 3) // "2024-01-15 10" +str := Time2Str(time.Now(), 4) // "2024-01-15 10:30" +str := Time2Str(time.Now(), 5) // "2024-01-15 10:30:00" + +// 特殊格式 +str := Time2Str(time.Now(), 12) // "01-15" +str := Time2Str(time.Now(), 14) // "01-15 10:30" +str := Time2Str(time.Now(), 34) // "10:30" +str := Time2Str(time.Now(), 35) // "10:30:00" +``` + +### 加密与随机 + +```go +// MD5 加密 +hash := Md5("password") // 32位小写MD5 + +// 随机数 +r := Rand(3) // 3位随机数 (0-999) +r := RandX(10, 100) // 10-100之间的随机数 +``` + +### 数学计算 + +```go +// 四舍五入保留小数 +f := Round(3.14159, 2) // 3.14 +f := Round(3.145, 2) // 3.15 +``` + +### 深拷贝 + +```go +// 深拷贝 Map/Slice(递归复制) +original := Map{"a": Map{"b": 1}} +copied := DeepCopyMap(original).(Map) + +// 修改副本不影响原始数据 +copied.GetMap("a")["b"] = 2 +// original["a"]["b"] 仍然是 1 +``` + +--- + +## 错误处理 + +### Error 类型 + +```go +type Error struct { + Logger *logrus.Logger // 可选的日志记录器 + error // 内嵌错误 +} +``` + +### 使用示例 + +```go +var e Error + +// 设置错误 +e.SetError(errors.New("something wrong")) + +// 获取错误 +if err := e.GetError(); err != nil { + fmt.Println(err) +} + +// 配合日志自动记录 +e.Logger = logrusLogger +e.SetError(errors.New("will be logged")) +``` + +### 在类型转换中使用 + +```go +var e Error +data := Map{"count": "abc"} + +count := data.GetInt("count", &e) +if e.GetError() != nil { + // 转换失败,count = 0 + fmt.Println("转换失败:", e.GetError()) +} +``` + +--- + +## 最佳实践 + +### 1. 链式调用处理请求数据 + +```go +func handler(that *Context) { + // 推荐:使用 Obj 链式调用 + userId := that.ReqData("user_id").ToInt() + page := that.ReqData("page").ToInt() + if page < 1 { + page = 1 + } + + // 处理 Map 数据 + user := that.Db.Get("user", "*", Map{"id": userId}) + if user != nil { + name := user.GetString("name") + age := user.GetInt("age") + } +} +``` + +### 2. 安全的类型转换 + +```go +// 带错误检查的转换 +var e Error +data := someMap.GetInt("key", &e) +if e.GetError() != nil { + // 使用默认值 + data = 0 +} + +// 简单场景直接转换(失败返回零值) +data := someMap.GetInt("key") // 失败返回 0 +``` + +### 3. 处理数据库查询结果 + +```go +// 查询返回 Map +user := that.Db.Get("user", "*", Map{"id": 1}) +if user != nil { + name := user.GetString("name") + createTime := user.GetTime("create_time") +} + +// 查询返回 []Map +users := that.Db.Select("user", "*", Map{"status": 1}) +for _, u := range users { + fmt.Printf("ID: %d, Name: %s\n", u.GetInt("id"), u.GetString("name")) +} +``` + +--- + +## 相关文档 + +- [快速上手指南](QUICKSTART.md) +- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) +- [代码生成器使用说明](CodeGen_使用说明.md) diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index d84ac32..67f666a 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -52,7 +52,8 @@ func main() { "port": "3306", "name": "your_database", "user": "root", - "password": "your_password" + "password": "your_password", + "prefix": "" } }, "cache": { @@ -65,13 +66,98 @@ func main() { } ``` -| 配置项 | 说明 | -|--------|------| -| `port` | 服务端口 | -| `mode` | 0=生产, 1=测试, 2=开发(输出SQL) | -| `tpt` | 静态文件目录 | -| `db` | 数据库配置 | -| `cache` | 缓存配置 | +### 配置项说明 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `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"] | 目录默认访问文件 | + +### 数据库配置 + +```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": "" + } + } +} +``` + +> MySQL 配置 `slave` 项即启用主从读写分离 + +### 缓存配置 + +```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 + } + } +} +``` + +缓存优先级: **Memory > Redis > DB**,自动穿透与回填 + +### 错误码配置 + +```json +{ + "error": { + "1": "内部系统异常", + "2": "访问权限异常", + "3": "请求参数异常", + "4": "数据处理异常", + "5": "数据结果异常" + } +} +``` + +> 自定义错误码建议从 10 开始 ## 路由系统 @@ -89,31 +175,134 @@ appIns.Run(Router{ }) ``` -### 获取请求参数 +### 路由路径 + +```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") -// JSON Body -that.Req.ParseForm() -data := that.Req.PostForm +// URL 参数 +id := that.Req.URL.Query().Get("id") -// 获取路径参数 -module := that.RouterPath[0] // 模块 -controller := that.RouterPath[1] // 控制器 -action := that.RouterPath[2] // 方法 +// 请求头 +token := that.Req.Header.Get("Authorization") ``` -### 响应数据 +## 响应数据 + +### Display 方法 ```go -// 标准 JSON 响应 -that.Display(0, Map{"data": "value"}) // 成功 code=0 -that.Display(1, "错误信息") // 失败 code=1 +// 成功响应 (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() { + // 自定义响应逻辑 +} ``` ## 中间件 @@ -121,6 +310,11 @@ that.Resp.Write([]byte("raw data")) ```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, "请先登录") @@ -135,9 +329,13 @@ appIns.SetConnectListener(func(that *Context) bool { ```go // Session 操作 that.Session("user_id", 123) // 设置 -userId := that.Session("user_id") // 获取 +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") // 获取 @@ -160,6 +358,12 @@ user := that.Db.Get("user", "*", Map{"id": 1}) // 插入 id := that.Db.Insert("user", Map{"name": "test", "age": 18}) +// 批量插入 +affected := that.Db.Inserts("user", []Map{ + {"name": "user1", "age": 20}, + {"name": "user2", "age": 25}, +}) + // 更新 rows := that.Db.Update("user", Map{"name": "new"}, Map{"id": 1}) @@ -205,6 +409,18 @@ success := that.Db.Action(func(tx db.HoTimeDB) bool { > **更多数据库操作**:参见 [HoTimeDB 使用说明](HoTimeDB_使用说明.md) +## 日志记录 + +```go +// 创建操作日志(自动插入 logs 表) +that.Log = Map{ + "type": "login", + "action": "用户登录", + "data": Map{"phone": phone}, +} +// 框架会自动添加 time, admin_id/user_id, ip 等字段 +``` + ## 扩展功能 | 功能 | 路径 | 说明 | @@ -233,7 +449,7 @@ func main() { // 登录检查中间件 appIns.SetConnectListener(func(that *Context) bool { // 放行登录接口 - if that.RouterPath[2] == "login" { + if len(that.RouterString) >= 3 && that.RouterString[2] == "login" { return false } if that.Session("user_id").Data == nil { @@ -247,16 +463,21 @@ func main() { "api": { "user": { "login": func(that *Context) { - phone := that.Req.FormValue("phone") - password := that.Req.FormValue("password") + 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": password, + "password": Md5(password), }) if user == nil { - that.Display(1, "账号或密码错误") + that.Display(3, "账号或密码错误") return } @@ -271,15 +492,15 @@ func main() { }, "list": func(that *Context) { - page := that.Req.FormValue("page") - if page == "" { - page = "1" + page := that.ReqData("page").ToInt() + if page == 0 { + page = 1 } users := that.Db.Table("user"). Where("status", 1). Order("id DESC"). - Page(ObjToInt(page), 10). + Page(page, 10). Select("id,name,phone,created_at") total := that.Db.Count("user", Map{"status": 1}) @@ -287,8 +508,14 @@ func main() { that.Display(0, Map{ "list": users, "total": total, + "page": page, }) }, + + "logout": func(that *Context) { + that.Session("user_id", nil) + that.Display(0, "退出成功") + }, }, }, })