Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf64276ab1 | |||
| 29a3b6095d | |||
| 650fafad1a | |||
| 6164dfe9bf | |||
| 5ba883cd6b | |||
| c2955d2500 | |||
| 0e1775f72b | |||
| 1a9e7e19b7 | |||
| 4828f3625c | |||
| 0318c95055 | |||
| f2f1fcc9aa | |||
| b755519fc6 | |||
| 3455fb0a1c | |||
| 5bb9ed77b8 | |||
| 678686fd48 | |||
| 9766648536 | |||
| 2cf20e7206 | |||
| 8cac1f5393 | |||
| 8337cdec0c | |||
| 46cecc47ea | |||
| 7535300107 | |||
| be41a70c76 | |||
| 5b407824a5 | |||
| 56f66fcaed | |||
| a298cb3d52 | |||
| 68f2c0fd8f | |||
| d314891126 | |||
| c0ae31f499 | |||
| 25edb6b5b7 | |||
| ead64dc776 | |||
| 876e3b1f45 | |||
| d6e940803d | |||
| 203e72f8a2 | |||
| 541c82410e | |||
| b680112fdb | |||
| 4ab5ab9ff4 | |||
| b425024359 | |||
| 78337c14ec | |||
| cdba8af129 | |||
| 9a7426180d | |||
| b7131603c4 | |||
| 2f3a5a0a59 | |||
| c2468a7389 | |||
| b6ba3486d6 | |||
| f59e909d30 | |||
| 3bb47de45a | |||
| c71973850e | |||
| 65f062b860 | |||
| 7b502fed36 | |||
| fd6b15bdaf | |||
| f31bf24ec2 | |||
| 30edb4d9fa | |||
| a32c3a1589 | |||
| 127e027940 | |||
| 2ee0838d8a | |||
| 72c23499f2 | |||
| 96d8868694 | |||
| 1533a57cb8 | |||
| 615f52d2e4 | |||
| f7dfd4ec77 | |||
| 2276aa12ef | |||
| 6505594fd0 | |||
| c55e26021b | |||
| 78420a692f | |||
| 6d9af149ad | |||
| 253cad35ef | |||
| 47c8736f34 | |||
| dcce8ace9e | |||
| 125ccc5c3b | |||
| 432d59d483 | |||
| 60f222b011 | |||
| 7a8b86ed12 | |||
| b940afa6b0 | |||
| 8afd00ff86 | |||
| 8992fd6d3a | |||
| 524a892480 | |||
| c420e23edb | |||
| 7419e03d67 | |||
| 9425544d1c | |||
| a8d78832df | |||
| adfb2236dc | |||
| 6034598bb4 | |||
| 6fe44cb1cb | |||
| 39d67d1775 | |||
| a8f9f12bb8 | |||
| 2c1abb1d11 | |||
| 21e0219568 | |||
| 952c3a1243 | |||
| 33cd7ebd6b | |||
| be02b3418d | |||
| f390617167 | |||
| f7410200e3 | |||
| 9cfdfbac78 | |||
| b015e78fa0 | |||
| bb9092c3e5 | |||
| cc1bad8d3f | |||
| 8f7380d796 | |||
| f37b9aee70 | |||
| 1ab9027c93 | |||
| 96d341aaab | |||
| 1f25f511cc | |||
| 07c7a628d1 | |||
| d7a59845eb | |||
| 98619083a1 | |||
| c8a9038efe | |||
| b638d8bd65 | |||
| 77753a123f |
@@ -0,0 +1,249 @@
|
||||
---
|
||||
name: 多数据库方言与前缀支持
|
||||
overview: 为 HoTimeDB ORM 实现完整的多数据库(MySQL/PostgreSQL/SQLite)方言支持和自动表前缀功能,采用智能解析+辅助方法兜底的混合策略,保持完全向后兼容。
|
||||
todos:
|
||||
- id: dialect-interface
|
||||
content: 扩展 Dialect 接口,添加 QuoteIdentifier 和 QuoteChar 方法
|
||||
status: completed
|
||||
- id: identifier-processor
|
||||
content: 新建 identifier.go,实现 IdentifierProcessor 及智能解析逻辑
|
||||
status: completed
|
||||
- id: db-integration
|
||||
content: 在 db.go 中集成处理器,添加 T() 和 C() 辅助方法
|
||||
status: completed
|
||||
- id: crud-update
|
||||
content: 修改 crud.go 中 Select/Insert/Update/Delete/buildJoin 等方法
|
||||
status: completed
|
||||
- id: where-update
|
||||
content: 修改 where.go 中 varCond 等条件处理方法
|
||||
status: completed
|
||||
- id: builder-check
|
||||
content: 检查 builder.go 是否需要额外修改
|
||||
status: completed
|
||||
- id: testing
|
||||
content: 编写测试用例验证多数据库和前缀功能
|
||||
status: completed
|
||||
- id: todo-1769037903242-d7aip6nh1
|
||||
content: ""
|
||||
status: pending
|
||||
---
|
||||
|
||||
# HoTimeDB 多数据库方言与自动前缀支持计划(更新版)
|
||||
|
||||
## 目标
|
||||
|
||||
1. **多数据库方言支持**:MySQL、PostgreSQL、SQLite 标识符引号自动转换
|
||||
2. **自动表前缀**:主表、JOIN 表、ON/WHERE 条件中的表名自动添加前缀
|
||||
3. **完全向后兼容**:用户现有写法无需修改
|
||||
4. **辅助方法兜底**:边缘情况可用 `T()` / `C()` 精确控制
|
||||
|
||||
## 混合策略设计
|
||||
|
||||
### 各部分处理方式
|
||||
|
||||
| 位置 | 处理方式 | 准确度 | 说明 |
|
||||
|
||||
|------|---------|--------|------|
|
||||
|
||||
| 主表名 | 自动 | 100% | `Select("order")` 自动处理 |
|
||||
|
||||
| JOIN 表名 | 自动 | 100% | `[><]order` 中提取表名处理 |
|
||||
|
||||
| ON 条件字符串 | 智能解析 | ~95% | 正则匹配 `table.column` 模式 |
|
||||
|
||||
| WHERE 条件 Map | 自动 | 100% | Map 的 key 是结构化的 |
|
||||
|
||||
| SELECT 字段 | 智能解析 | ~95% | 同 ON 条件 |
|
||||
|
||||
### 辅助方法(兜底)
|
||||
|
||||
```go
|
||||
db.T("order") // 返回 "`app_order`" (MySQL) 或 "\"app_order\"" (PG)
|
||||
db.C("order", "name") // 返回 "`app_order`.`name`"
|
||||
db.C("order.name") // 同上,支持点号格式
|
||||
```
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 第1步:扩展 Dialect 接口([db/dialect.go](db/dialect.go))
|
||||
|
||||
添加新方法到 `Dialect` 接口:
|
||||
|
||||
```go
|
||||
// QuoteIdentifier 处理单个标识符(去除已有引号,添加正确引号)
|
||||
QuoteIdentifier(name string) string
|
||||
|
||||
// QuoteChar 返回引号字符
|
||||
QuoteChar() string
|
||||
```
|
||||
|
||||
三种方言实现:
|
||||
|
||||
- MySQL: 反引号 `` ` ``
|
||||
- PostgreSQL/SQLite: 双引号 `"`
|
||||
|
||||
### 第2步:添加标识符处理器([db/identifier.go](db/identifier.go) 新文件)
|
||||
|
||||
```go
|
||||
type IdentifierProcessor struct {
|
||||
dialect Dialect
|
||||
prefix string
|
||||
}
|
||||
|
||||
// ProcessTableName 处理表名(添加前缀+引号)
|
||||
// "order" → "`app_order`"
|
||||
func (p *IdentifierProcessor) ProcessTableName(name string) string
|
||||
|
||||
// ProcessColumn 处理 table.column 格式
|
||||
// "order.name" → "`app_order`.`name`"
|
||||
// "`order`.name" → "`app_order`.`name`"
|
||||
func (p *IdentifierProcessor) ProcessColumn(name string) string
|
||||
|
||||
// ProcessConditionString 智能解析条件字符串
|
||||
// "user.id = order.user_id" → "`app_user`.`id` = `app_order`.`user_id`"
|
||||
func (p *IdentifierProcessor) ProcessConditionString(condition string) string
|
||||
|
||||
// ProcessFieldList 处理字段列表字符串
|
||||
// "order.id, user.name AS uname" → "`app_order`.`id`, `app_user`.`name` AS uname"
|
||||
func (p *IdentifierProcessor) ProcessFieldList(fields string) string
|
||||
```
|
||||
|
||||
**智能解析正则**:
|
||||
|
||||
```go
|
||||
// 匹配 table.column 模式,排除已有引号、函数调用等
|
||||
// 模式: \b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b
|
||||
// 排除: `table`.column, "table".column, FUNC(), 123.456
|
||||
```
|
||||
|
||||
### 第3步:在 HoTimeDB 中集成([db/db.go](db/db.go))
|
||||
|
||||
```go
|
||||
// processor 缓存
|
||||
var processorOnce sync.Once
|
||||
var processor *IdentifierProcessor
|
||||
|
||||
// GetProcessor 获取标识符处理器
|
||||
func (that *HoTimeDB) GetProcessor() *IdentifierProcessor
|
||||
|
||||
// T 辅助方法:获取带前缀和引号的表名
|
||||
func (that *HoTimeDB) T(table string) string
|
||||
|
||||
// C 辅助方法:获取带前缀和引号的 table.column
|
||||
func (that *HoTimeDB) C(args ...string) string
|
||||
```
|
||||
|
||||
### 第4步:修改 CRUD 方法([db/crud.go](db/crud.go))
|
||||
|
||||
**Select 方法改动**:
|
||||
|
||||
```go
|
||||
// L112-116 原代码
|
||||
if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") {
|
||||
query += " FROM `" + that.Prefix + table + "` "
|
||||
} else {
|
||||
query += " FROM " + that.Prefix + table + " "
|
||||
}
|
||||
|
||||
// 改为
|
||||
query += " FROM " + that.GetProcessor().ProcessTableName(table) + " "
|
||||
|
||||
// 字段列表处理(L90-107)
|
||||
// 如果是字符串,调用 ProcessFieldList 处理
|
||||
```
|
||||
|
||||
**buildJoin 方法改动**(L156-222):
|
||||
|
||||
```go
|
||||
// 原代码 L186-190
|
||||
table := Substr(k, 3, len(k)-3)
|
||||
if !strings.Contains(table, " ") {
|
||||
table = "`" + table + "`"
|
||||
}
|
||||
query += " LEFT JOIN " + table + " ON " + v.(string) + " "
|
||||
|
||||
// 改为
|
||||
table := Substr(k, 3, len(k)-3)
|
||||
table = that.GetProcessor().ProcessTableName(table)
|
||||
onCondition := that.GetProcessor().ProcessConditionString(v.(string))
|
||||
query += " LEFT JOIN " + table + " ON " + onCondition + " "
|
||||
```
|
||||
|
||||
**Insert/Inserts/Update/Delete** 同样修改表名和字段名处理。
|
||||
|
||||
### 第5步:修改 WHERE 条件处理([db/where.go](db/where.go))
|
||||
|
||||
**varCond 方法改动**(多处):
|
||||
|
||||
```go
|
||||
// 原代码(多处出现)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "`"
|
||||
}
|
||||
|
||||
// 改为
|
||||
k = that.GetProcessor().ProcessColumn(k)
|
||||
```
|
||||
|
||||
需要修改的函数:
|
||||
|
||||
- `varCond` (L205-338)
|
||||
- `handleDefaultCondition` (L340-368)
|
||||
- `handlePlainField` (L370-400)
|
||||
|
||||
### 第6步:修改链式构建器([db/builder.go](db/builder.go))
|
||||
|
||||
**LeftJoin 等方法需要传递处理器**:
|
||||
|
||||
由于 builder 持有 HoTimeDB 引用,可以直接使用:
|
||||
|
||||
```go
|
||||
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
// 不在这里处理,让 buildJoin 统一处理
|
||||
that.Join(Map{"[>]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
```
|
||||
|
||||
JOIN 的实际处理在 `crud.go` 的 `buildJoin` 中完成。
|
||||
|
||||
## 智能解析的边界处理
|
||||
|
||||
### 会自动处理的情况
|
||||
|
||||
- `user.id = order.user_id` → 正确处理
|
||||
- `user.id=order.user_id` → 正确处理(无空格)
|
||||
- `` `user`.id = order.user_id `` → 正确处理(混合格式)
|
||||
- `user.id = order.user_id AND order.status = 1` → 正确处理
|
||||
|
||||
### 需要辅助方法的边缘情况
|
||||
|
||||
- 子查询中的表名
|
||||
- 复杂 CASE WHEN 表达式
|
||||
- 动态拼接的 SQL 片段
|
||||
|
||||
## 文件清单
|
||||
|
||||
| 文件 | 操作 | 说明 |
|
||||
|
||||
|------|------|------|
|
||||
|
||||
| [db/dialect.go](db/dialect.go) | 修改 | 扩展 Dialect 接口 |
|
||||
|
||||
| [db/identifier.go](db/identifier.go) | 新增 | IdentifierProcessor 实现 |
|
||||
|
||||
| [db/db.go](db/db.go) | 修改 | 集成处理器,添加 T()/C() 方法 |
|
||||
|
||||
| [db/crud.go](db/crud.go) | 修改 | 修改所有 CRUD 方法 |
|
||||
|
||||
| [db/where.go](db/where.go) | 修改 | 修改条件处理逻辑 |
|
||||
|
||||
| [db/builder.go](db/builder.go) | 检查 | 可能无需修改(buildJoin 统一处理)|
|
||||
|
||||
## 测试用例
|
||||
|
||||
1. **多数据库切换**:MySQL → PostgreSQL → SQLite
|
||||
2. **前缀场景**:有前缀 vs 无前缀
|
||||
3. **复杂 JOIN**:多表 JOIN + 复杂 ON 条件
|
||||
4. **混合写法**:`order.name` + `` `user`.id `` 混用
|
||||
5. **辅助方法**:`T()` 和 `C()` 正确性
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
name: 集成请求参数获取方法
|
||||
overview: 在 context.go 中为 Context 结构体添加五个请求参数获取方法:ReqData(统一获取)、ReqDataParams(URL参数)、ReqDataJson(JSON Body)、ReqDataForm(表单数据)、ReqFile(获取上传文件)。
|
||||
todos:
|
||||
- id: add-imports
|
||||
content: 在 context.go 中添加 bytes、io、mime/multipart 包的导入
|
||||
status: completed
|
||||
- id: impl-params
|
||||
content: 实现 ReqParam/ReqParams 方法(获取 URL 参数,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-form
|
||||
content: 实现 ReqForm/ReqForms 方法(获取表单数据,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-json
|
||||
content: 实现 ReqJson/ReqJsons 方法(获取 JSON Body,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-file
|
||||
content: 实现 ReqFile/ReqFiles 方法(获取上传文件)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-reqdata
|
||||
content: 实现 ReqData/ReqDatas 方法(统一获取,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- impl-params
|
||||
- impl-form
|
||||
- impl-json
|
||||
---
|
||||
|
||||
# 集成请求参数获取方法
|
||||
|
||||
## 实现位置
|
||||
|
||||
在 [`context.go`](context.go) 中添加请求参数获取方法,**风格与 `Session("key")` 保持一致,返回 `*Obj` 支持链式调用**。
|
||||
|
||||
## 新增方法
|
||||
|
||||
### 1. ReqParam - 获取 URL 查询参数(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 获取单个参数,支持链式调用
|
||||
func (that *Context) ReqParam(key string) *Obj {
|
||||
// that.ReqParam("id").ToStr()
|
||||
// that.ReqParam("id").ToInt()
|
||||
}
|
||||
|
||||
// 获取所有 URL 参数
|
||||
func (that *Context) ReqParams() Map
|
||||
```
|
||||
|
||||
### 2. ReqForm - 获取表单数据(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 获取单个表单字段
|
||||
func (that *Context) ReqForm(key string) *Obj {
|
||||
// that.ReqForm("name").ToStr()
|
||||
}
|
||||
|
||||
// 获取所有表单数据
|
||||
func (that *Context) ReqForms() Map
|
||||
```
|
||||
|
||||
### 3. ReqJson - 获取 JSON Body(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 获取 JSON 中的单个字段
|
||||
func (that *Context) ReqJson(key string) *Obj {
|
||||
// that.ReqJson("data").ToMap()
|
||||
// that.ReqJson("count").ToInt()
|
||||
}
|
||||
|
||||
// 获取完整 JSON Body
|
||||
func (that *Context) ReqJsons() Map
|
||||
```
|
||||
|
||||
### 4. ReqFile - 获取上传文件
|
||||
|
||||
```go
|
||||
func (that *Context) ReqFile(name string) (multipart.File, *multipart.FileHeader, error)
|
||||
func (that *Context) ReqFiles(name string) ([]*multipart.FileHeader, error)
|
||||
```
|
||||
|
||||
### 5. ReqData - 统一获取参数(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 统一获取(JSON > Form > URL),支持链式调用
|
||||
func (that *Context) ReqData(key string) *Obj {
|
||||
// that.ReqData("id").ToStr()
|
||||
}
|
||||
|
||||
// 获取所有合并后的参数
|
||||
func (that *Context) ReqDatas() Map
|
||||
```
|
||||
|
||||
## 需要的导入
|
||||
|
||||
```go
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
)
|
||||
```
|
||||
|
||||
## 关键实现细节
|
||||
|
||||
1. **Body 只能读取一次的问题**:读取 Body 后需要用 `io.NopCloser(bytes.NewBuffer(body))` 恢复,以便后续代码(如其他中间件)还能再次读取
|
||||
|
||||
2. **废弃 API 替换**:使用 `io.ReadAll` 替代已废弃的 `ioutil.ReadAll`
|
||||
|
||||
3. **多值参数处理**:当同一参数有多个值时(如 `?id=1&id=2`),存储为 `Slice`;单值则直接存储字符串
|
||||
|
||||
## 使用示例
|
||||
|
||||
```go
|
||||
appIns.Run(Router{
|
||||
"app": {
|
||||
"user": {
|
||||
"info": func(that *Context) {
|
||||
// 链式调用获取单个参数(类似 Session 风格)
|
||||
id := that.ReqData("id").ToInt() // 统一获取
|
||||
name := that.ReqParam("name").ToStr() // URL 参数
|
||||
age := that.ReqForm("age").ToCeilInt() // 表单参数
|
||||
data := that.ReqJson("profile").ToMap() // JSON 字段
|
||||
|
||||
// 获取所有参数(返回 Map)
|
||||
allParams := that.ReqDatas() // 合并后的所有参数
|
||||
urlParams := that.ReqParams() // 所有 URL 参数
|
||||
formData := that.ReqForms() // 所有表单数据
|
||||
jsonBody := that.ReqJsons() // 完整 JSON Body
|
||||
|
||||
that.Display(0, Map{"id": id, "name": name})
|
||||
},
|
||||
"upload": func(that *Context) {
|
||||
// 获取单个上传文件
|
||||
file, header, err := that.ReqFile("avatar")
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
// header.Filename - 文件名
|
||||
// header.Size - 文件大小
|
||||
}
|
||||
|
||||
// 获取多个同名上传文件
|
||||
files, err := that.ReqFiles("images")
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## 风格对比
|
||||
|
||||
| 旧方式(需要类型断言) | 新方式(链式调用) |
|
||||
|
||||
|------------------------|-------------------|
|
||||
|
||||
| `req["id"].(string) `| `that.ReqData("id").ToStr()` |
|
||||
|
||||
| `ObjToInt(req["id"]) `| `that.ReqData("id").ToInt()` |
|
||||
|
||||
| 需要手动处理 nil | `*Obj` 自动处理空值 |
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"setup-worktree": [
|
||||
"npm install"
|
||||
]
|
||||
}
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
/.idea/*
|
||||
.idea
|
||||
/example/config/app.json
|
||||
/example/tpt/demo/
|
||||
*.exe
|
||||
/example/config
|
||||
@@ -1,6 +1,84 @@
|
||||
# hotime
|
||||
golang web服务框架
|
||||
支持数据库db:mysql、sqlite3
|
||||
支持缓存cache:redis,memory,数据库
|
||||
自带工具类,上下文,以及session等功能
|
||||
# HoTime
|
||||
|
||||
**高性能 Go Web 服务框架**
|
||||
|
||||
一个"小而全"的 Go Web 框架,内置 ORM、三级缓存、Session 管理,让你专注于业务逻辑。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- **高性能** - 单机 10万+ QPS,支持百万级并发用户
|
||||
- **内置 ORM** - 类 Medoo 语法,链式查询,支持 MySQL/SQLite/PostgreSQL
|
||||
- **三级缓存** - Memory > Redis > DB,自动穿透与回填
|
||||
- **Session 管理** - 内置会话管理,支持多种存储后端
|
||||
- **代码生成** - 根据数据库表自动生成 CRUD 接口
|
||||
- **开箱即用** - 微信支付/公众号/小程序、阿里云、腾讯云等 SDK 内置
|
||||
|
||||
## 文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [快速上手指南](docs/QUICKSTART.md) | 5 分钟入门,安装配置、路由、中间件、基础数据库操作 |
|
||||
| [HoTimeDB 使用说明](docs/HoTimeDB_使用说明.md) | 完整数据库 ORM 教程 |
|
||||
| [HoTimeDB API 参考](docs/HoTimeDB_API参考.md) | 数据库 API 速查手册 |
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get code.hoteas.com/golang/hotime
|
||||
```
|
||||
|
||||
## 性能
|
||||
|
||||
| 并发数 | QPS | 成功率 | 平均延迟 |
|
||||
|--------|-----|--------|----------|
|
||||
| 500 | 99,960 | 100% | 5.0ms |
|
||||
| **1000** | **102,489** | **100%** | **9.7ms** |
|
||||
| 2000 | 75,801 | 99.99% | 26.2ms |
|
||||
|
||||
> 测试环境:24 核 CPU,Windows 10,Go 1.19.3
|
||||
|
||||
### 并发用户估算
|
||||
|
||||
| 使用场景 | 请求频率 | 可支持用户数 |
|
||||
|----------|----------|--------------|
|
||||
| 高频交互 | 1次/秒 | ~10万 |
|
||||
| 活跃用户 | 1次/5秒 | ~50万 |
|
||||
| 普通浏览 | 1次/10秒 | ~100万 |
|
||||
|
||||
## 框架对比
|
||||
|
||||
| 特性 | HoTime | Gin | Echo | Fiber |
|
||||
|------|--------|-----|------|-------|
|
||||
| 性能 | 100K QPS | 70K QPS | 70K QPS | 100K QPS |
|
||||
| 内置ORM | ✅ | ❌ | ❌ | ❌ |
|
||||
| 内置缓存 | ✅ 三级缓存 | ❌ | ❌ | ❌ |
|
||||
| Session | ✅ 内置 | ❌ 需插件 | ❌ 需插件 | ❌ 需插件 |
|
||||
| 代码生成 | ✅ | ❌ | ❌ | ❌ |
|
||||
| 微信/支付集成 | ✅ 内置 | ❌ | ❌ | ❌ |
|
||||
|
||||
## 适用场景
|
||||
|
||||
| 场景 | 推荐度 | 说明 |
|
||||
|------|--------|------|
|
||||
| 中小型后台系统 | ⭐⭐⭐⭐⭐ | 完美适配,开发效率最高 |
|
||||
| 微信小程序后端 | ⭐⭐⭐⭐⭐ | 内置微信 SDK |
|
||||
| 快速原型开发 | ⭐⭐⭐⭐⭐ | 代码生成 + 全功能集成 |
|
||||
| 高并发 API 服务 | ⭐⭐⭐⭐ | 性能足够 |
|
||||
| 大型微服务 | ⭐⭐⭐ | 建议用 Gin/Echo |
|
||||
|
||||
## 扩展功能
|
||||
|
||||
- 微信支付/公众号/小程序 - `dri/wechat/`
|
||||
- 阿里云服务 - `dri/aliyun/`
|
||||
- 腾讯云服务 - `dri/tencent/`
|
||||
- 文件上传下载 - `dri/upload/`, `dri/download/`
|
||||
- MongoDB - `dri/mongodb/`
|
||||
- RSA 加解密 - `dri/rsa/`
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
**HoTime** - 让 Go Web 开发更简单、更高效
|
||||
|
||||
+96
-83
@@ -7,8 +7,8 @@ import (
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
. "code.hoteas.com/golang/hotime/log"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -23,7 +23,6 @@ type Application struct {
|
||||
MakeCodeRouter map[string]*code.MakeCode
|
||||
MethodRouter
|
||||
Router
|
||||
ContextBase
|
||||
Error
|
||||
Log *logrus.Logger
|
||||
WebConnectLog *logrus.Logger
|
||||
@@ -69,14 +68,24 @@ func (that *Application) Run(router Router) {
|
||||
if that.Router == nil {
|
||||
that.Router = Router{}
|
||||
}
|
||||
for k, v := range router {
|
||||
for k, _ := range router {
|
||||
v := router[k]
|
||||
if that.Router[k] == nil {
|
||||
that.Router[k] = v
|
||||
}
|
||||
//直达接口层复用
|
||||
for k1, _ := range v {
|
||||
v1 := v[k1]
|
||||
if that.Router[k][k1] == nil {
|
||||
that.Router[k][k1] = v1
|
||||
}
|
||||
|
||||
for k1, v1 := range v {
|
||||
that.Router[k][k1] = v1
|
||||
for k2, _ := range v1 {
|
||||
v2 := v1[k2]
|
||||
that.Router[k][k1][k2] = v2
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//重新设置MethodRouter//直达路由
|
||||
that.MethodRouter = MethodRouter{}
|
||||
@@ -230,10 +239,8 @@ func (that *Application) SetConfig(configPath ...string) {
|
||||
|
||||
}
|
||||
|
||||
that.Log = GetLog(that.Config.GetString("logFile"), true)
|
||||
that.Error = Error{Logger: that.Log}
|
||||
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
|
||||
that.WebConnectLog = GetLog(that.Config.GetString("webConnectLogFile"), false)
|
||||
if that.Error.GetError() != nil {
|
||||
fmt.Println(that.Error.GetError().Error())
|
||||
}
|
||||
|
||||
//文件如果损坏则不写入配置防止配置文件数据丢失
|
||||
@@ -260,6 +267,12 @@ func (that *Application) SetConfig(configPath ...string) {
|
||||
|
||||
}
|
||||
|
||||
that.Log = GetLog(that.Config.GetString("logFile"), true)
|
||||
that.Error = Error{Logger: that.Log}
|
||||
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
|
||||
that.WebConnectLog = GetLog(that.Config.GetString("webConnectLogFile"), false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输
|
||||
@@ -272,7 +285,7 @@ func (that *Application) SetConnectListener(lis func(that *Context) (isFinished
|
||||
//
|
||||
//}
|
||||
|
||||
//序列化链接
|
||||
// 序列化链接
|
||||
func (that *Application) urlSer(url string) (string, []string) {
|
||||
q := strings.Index(url, "?")
|
||||
if q == -1 {
|
||||
@@ -318,6 +331,9 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
if len(token) == 32 {
|
||||
sessionId = token
|
||||
//没有token,则查阅session
|
||||
if cookie == nil || cookie.Value != sessionId {
|
||||
needSetCookie = sessionId
|
||||
}
|
||||
} else if err == nil && cookie.Value != "" {
|
||||
sessionId = cookie.Value
|
||||
//session也没有则判断是否创建cookie
|
||||
@@ -346,14 +362,18 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
//是否展示日志
|
||||
if that.WebConnectLog != nil {
|
||||
ipStr := Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))
|
||||
|
||||
//负载均衡优化
|
||||
if ipStr == "127.0.0.1" {
|
||||
if req.Header.Get("X-Forwarded-For") != "" {
|
||||
ipStr = req.Header.Get("X-Forwarded-For")
|
||||
} else if req.Header.Get("X-Real-IP") != "" {
|
||||
ipStr = req.Header.Get("X-Real-IP")
|
||||
}
|
||||
ipStr := ""
|
||||
if req.Header.Get("X-Forwarded-For") != "" {
|
||||
ipStr = req.Header.Get("X-Forwarded-For")
|
||||
} else if req.Header.Get("X-Real-IP") != "" {
|
||||
ipStr = req.Header.Get("X-Real-IP")
|
||||
}
|
||||
//负载均衡优化
|
||||
if ipStr == "" {
|
||||
//RemoteAddr := that.Req.RemoteAddr
|
||||
ipStr = Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))
|
||||
}
|
||||
|
||||
that.WebConnectLog.Infoln(ipStr, context.Req.Method,
|
||||
@@ -363,15 +383,17 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
}()
|
||||
|
||||
//访问拦截true继续false暂停
|
||||
connectListenerLen := len(that.connectListener)
|
||||
|
||||
for i := connectListenerLen - 1; i >= 0; i-- {
|
||||
|
||||
if that.connectListener[i](&context) {
|
||||
connectListenerLen := len(that.connectListener) - 1
|
||||
|
||||
for true {
|
||||
if connectListenerLen < 0 {
|
||||
break
|
||||
}
|
||||
if that.connectListener[connectListenerLen](&context) {
|
||||
context.View()
|
||||
return
|
||||
}
|
||||
connectListenerLen--
|
||||
}
|
||||
|
||||
//接口服务
|
||||
@@ -445,6 +467,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
if context.Config.GetString("crossDomain") == "" {
|
||||
if sessionId != "" {
|
||||
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
//context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
|
||||
}
|
||||
|
||||
return
|
||||
@@ -474,7 +497,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
|
||||
|
||||
if sessionId != "" {
|
||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||
@@ -489,7 +512,8 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) {
|
||||
|
||||
if sessionId != "" {
|
||||
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
//http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
|
||||
}
|
||||
|
||||
return
|
||||
@@ -520,7 +544,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
|
||||
|
||||
if sessionId != "" {
|
||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||
@@ -529,7 +553,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
|
||||
}
|
||||
|
||||
//Init 初始化application
|
||||
// Init 初始化application
|
||||
func Init(config string) *Application {
|
||||
appIns := Application{}
|
||||
//手动模式,
|
||||
@@ -555,13 +579,9 @@ func Init(config string) *Application {
|
||||
codeMake["name"] = codeMake.GetString("table")
|
||||
}
|
||||
|
||||
if appIns.Config.GetInt("mode") > 0 {
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{}
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(&appIns.Db, codeMake)
|
||||
} else {
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{}
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(nil, codeMake)
|
||||
}
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{Error: appIns.Error}
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(&appIns.Db, codeMake)
|
||||
|
||||
//接入动态代码层
|
||||
if appIns.Router == nil {
|
||||
appIns.Router = Router{}
|
||||
@@ -569,20 +589,30 @@ func Init(config string) *Application {
|
||||
|
||||
//appIns.Router[codeMake.GetString("name")] = TptProject
|
||||
appIns.Router[codeMake.GetString("name")] = Proj{}
|
||||
|
||||
for k2, _ := range TptProject {
|
||||
appIns.Router[codeMake.GetString("name")][k2] = Ctr{}
|
||||
for k3, v3 := range TptProject[k2] {
|
||||
if appIns.Router[codeMake.GetString("name")][k2] == nil {
|
||||
appIns.Router[codeMake.GetString("name")][k2] = Ctr{}
|
||||
}
|
||||
for k3, _ := range TptProject[k2] {
|
||||
v3 := TptProject[k2][k3]
|
||||
appIns.Router[codeMake.GetString("name")][k2][k3] = v3
|
||||
}
|
||||
}
|
||||
|
||||
for k1, _ := range appIns.MakeCodeRouter[codeMake.GetString("name")].TableColumns {
|
||||
appIns.Router[codeMake.GetString("name")][k1] = appIns.Router[codeMake.GetString("name")]["hotimeCommon"]
|
||||
if appIns.Router[codeMake.GetString("name")][k1] == nil {
|
||||
appIns.Router[codeMake.GetString("name")][k1] = Ctr{}
|
||||
}
|
||||
|
||||
for k2, _ := range appIns.Router[codeMake.GetString("name")]["hotimeCommon"] {
|
||||
//golang毛病
|
||||
v2 := appIns.Router[codeMake.GetString("name")]["hotimeCommon"][k2]
|
||||
appIns.Router[codeMake.GetString("name")][k1][k2] = v2
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
setMakeCodeLintener(codeMake.GetString("name"), &appIns)
|
||||
}()
|
||||
setMakeCodeListener(codeMake.GetString("name"), &appIns)
|
||||
|
||||
}
|
||||
|
||||
@@ -608,6 +638,8 @@ func SetMysqlDB(appIns *Application, config Map) {
|
||||
appIns.Db.Type = "mysql"
|
||||
appIns.Db.DBName = config.GetString("name")
|
||||
appIns.Db.Prefix = config.GetString("prefix")
|
||||
appIns.Db.Log = appIns.Log
|
||||
appIns.Db.Mode = appIns.Config.GetCeilInt("mode")
|
||||
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
|
||||
//master数据库配置
|
||||
query := config.GetString("user") + ":" + config.GetString("password") +
|
||||
@@ -637,6 +669,8 @@ func SetSqliteDB(appIns *Application, config Map) {
|
||||
|
||||
appIns.Db.Type = "sqlite"
|
||||
appIns.Db.Prefix = config.GetString("prefix")
|
||||
appIns.Db.Mode = appIns.Config.GetCeilInt("mode")
|
||||
appIns.Db.Log = appIns.Log
|
||||
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
|
||||
db, e := sql.Open("sqlite3", config.GetString("path"))
|
||||
if e != nil && len(err) != 0 {
|
||||
@@ -648,52 +682,10 @@ func SetSqliteDB(appIns *Application, config Map) {
|
||||
})
|
||||
}
|
||||
|
||||
func setMakeCodeLintener(name string, appIns *Application) {
|
||||
func setMakeCodeListener(name string, appIns *Application) {
|
||||
appIns.SetConnectListener(func(context *Context) (isFinished bool) {
|
||||
|
||||
codeIns := appIns.MakeCodeRouter[name]
|
||||
//文件上传接口
|
||||
if len(context.RouterString) == 1 && context.RouterString[0] == "file" && context.Req.Method == "POST" {
|
||||
if context.Session(codeIns.FileConfig.GetString("table")+"_id").Data == nil {
|
||||
context.Display(2, "你还没有登录")
|
||||
return true
|
||||
}
|
||||
//读取网络文件
|
||||
fi, fheader, err := context.Req.FormFile("file")
|
||||
if err != nil {
|
||||
context.Display(3, err)
|
||||
return true
|
||||
|
||||
}
|
||||
filePath := context.Config.GetString("filePath")
|
||||
if filePath == "" {
|
||||
filePath = "/file/2006/01/02/"
|
||||
}
|
||||
|
||||
path := time.Now().Format(filePath)
|
||||
e := os.MkdirAll(context.Config.GetString("tpt")+path, os.ModeDir)
|
||||
if e != nil {
|
||||
context.Display(3, e)
|
||||
return true
|
||||
}
|
||||
filePath = path + Md5(ObjToStr(RandX(100000, 9999999))) + fheader.Filename[strings.LastIndex(fheader.Filename, "."):]
|
||||
newFile, e := os.Create(context.Config.GetString("tpt") + filePath)
|
||||
|
||||
if e != nil {
|
||||
context.Display(3, e)
|
||||
return true
|
||||
}
|
||||
|
||||
_, e = io.Copy(newFile, fi)
|
||||
|
||||
if e != nil {
|
||||
context.Display(3, e)
|
||||
return true
|
||||
}
|
||||
|
||||
context.Display(0, filePath)
|
||||
return true
|
||||
}
|
||||
|
||||
if len(context.RouterString) < 2 || appIns.MakeCodeRouter[context.RouterString[0]] == nil {
|
||||
return isFinished
|
||||
@@ -707,6 +699,12 @@ func setMakeCodeLintener(name string, appIns *Application) {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
if context.RouterString[1] == "hotime" && context.RouterString[2] == "config" {
|
||||
return isFinished
|
||||
}
|
||||
if context.RouterString[1] == "hotime" && context.RouterString[2] == "wallpaper" {
|
||||
return isFinished
|
||||
}
|
||||
if context.Session(codeIns.FileConfig.GetString("table")+"_id").Data == nil {
|
||||
context.Display(2, "你还没有登录")
|
||||
return true
|
||||
@@ -724,6 +722,11 @@ func setMakeCodeLintener(name string, appIns *Application) {
|
||||
context.Req.Method != "POST" {
|
||||
return isFinished
|
||||
}
|
||||
//排除已有接口的无效操作
|
||||
if len(context.RouterString) == 3 && context.Router[context.RouterString[0]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]][context.RouterString[2]] != nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
//列表检索
|
||||
if len(context.RouterString) == 2 &&
|
||||
context.Req.Method == "GET" {
|
||||
@@ -744,6 +747,16 @@ func setMakeCodeLintener(name string, appIns *Application) {
|
||||
context.Req.Method == "POST" {
|
||||
return isFinished
|
||||
}
|
||||
//分析
|
||||
if len(context.RouterString) == 3 && context.RouterString[2] == "analyse" &&
|
||||
context.Req.Method == "GET" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["analyse"] == nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["analyse"](context)
|
||||
}
|
||||
//查询单条
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "GET" {
|
||||
@@ -775,7 +788,7 @@ func setMakeCodeLintener(name string, appIns *Application) {
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["remove"](context)
|
||||
}
|
||||
|
||||
context.View()
|
||||
//context.View()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
Vendored
+10
-10
@@ -1,12 +1,13 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"errors"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// HoTimeCache 可配置memory,db,redis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,
|
||||
//缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
|
||||
// 缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
|
||||
type HoTimeCache struct {
|
||||
*Error
|
||||
dbCache *CacheDb
|
||||
@@ -92,7 +93,7 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
//db缓存有
|
||||
if that.dbCache != nil && that.dbCache.DbSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
@@ -119,7 +120,7 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
|
||||
if that.redisCache != nil && that.redisCache.DbSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
//db缓存有
|
||||
if that.dbCache != nil && that.dbCache.DbSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
@@ -132,7 +133,7 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
||||
//内存缓存有
|
||||
if that.memoryCache != nil {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
if reData != nil {
|
||||
if reData != nil && reData.Data != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
@@ -140,8 +141,7 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
||||
//redis缓存有
|
||||
if that.redisCache != nil {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
|
||||
if reData != nil && reData.Data != nil {
|
||||
if that.memoryCache != nil {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
@@ -149,10 +149,10 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
//db缓存有
|
||||
if that.dbCache != nil {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if reData != nil && reData.Data != nil {
|
||||
if that.memoryCache != nil {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
||||
if that.redisCache != nil {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
//db缓存有
|
||||
if that.dbCache != nil {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -87,7 +87,7 @@ func (that *CacheDb) initDbTable() {
|
||||
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
// 获取Cache键只能为string类型
|
||||
func (that *CacheDb) get(key string) interface{} {
|
||||
|
||||
cached := that.Db.Get("cached", "*", Map{"key": key})
|
||||
@@ -108,7 +108,7 @@ func (that *CacheDb) get(key string) interface{} {
|
||||
return data.Get("data")
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
// key value ,时间为时间戳
|
||||
func (that *CacheDb) set(key string, value interface{}, tim int64) {
|
||||
|
||||
bte, _ := json.Marshal(Map{"data": value})
|
||||
|
||||
Vendored
+65
-108
@@ -7,14 +7,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheMemory 基于 sync.Map 的缓存实现
|
||||
type CacheMemory struct {
|
||||
TimeOut int64
|
||||
DbSet bool
|
||||
SessionSet bool
|
||||
Map
|
||||
*Error
|
||||
ContextBase
|
||||
mutex *sync.RWMutex
|
||||
cache sync.Map // 替代传统的 Map
|
||||
}
|
||||
|
||||
func (that *CacheMemory) GetError() *Error {
|
||||
@@ -26,133 +25,91 @@ func (that *CacheMemory) GetError() *Error {
|
||||
func (that *CacheMemory) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
func (c *CacheMemory) get(key string) (res *Obj) {
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (that *CacheMemory) get(key string) interface{} {
|
||||
that.Error.SetError(nil)
|
||||
if that.Map == nil {
|
||||
that.Map = Map{}
|
||||
res = &Obj{
|
||||
Error: *c.Error,
|
||||
}
|
||||
value, ok := c.cache.Load(key)
|
||||
if !ok {
|
||||
return res // 缓存不存在
|
||||
}
|
||||
|
||||
if that.Map[key] == nil {
|
||||
return nil
|
||||
}
|
||||
data := that.Map.Get(key, that.Error).(cacheData)
|
||||
if that.Error.GetError() != nil {
|
||||
return nil
|
||||
}
|
||||
data := value.(cacheData)
|
||||
|
||||
// 检查是否过期
|
||||
if data.time < time.Now().Unix() {
|
||||
delete(that.Map, key)
|
||||
return nil
|
||||
c.cache.Delete(key) // 删除过期缓存
|
||||
return res
|
||||
}
|
||||
return data.data
|
||||
res.Data = data.data
|
||||
return res
|
||||
}
|
||||
|
||||
func (that *CacheMemory) refreshMap() {
|
||||
|
||||
func (c *CacheMemory) set(key string, value interface{}, expireAt int64) {
|
||||
data := cacheData{
|
||||
data: value,
|
||||
time: expireAt,
|
||||
}
|
||||
c.cache.Store(key, data)
|
||||
}
|
||||
func (c *CacheMemory) delete(key string) {
|
||||
if strings.Contains(key, "*") {
|
||||
// 通配符删除
|
||||
prefix := strings.TrimSuffix(key, "*")
|
||||
c.cache.Range(func(k, v interface{}) bool {
|
||||
if strings.HasPrefix(k.(string), prefix) {
|
||||
c.cache.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
// 精确删除
|
||||
c.cache.Delete(key)
|
||||
}
|
||||
}
|
||||
func (c *CacheMemory) refreshMap() {
|
||||
go func() {
|
||||
that.mutex.Lock()
|
||||
defer that.mutex.Unlock()
|
||||
for key, v := range that.Map {
|
||||
data := v.(cacheData)
|
||||
if data.time <= time.Now().Unix() {
|
||||
delete(that.Map, key)
|
||||
now := time.Now().Unix()
|
||||
c.cache.Range(func(key, value interface{}) bool {
|
||||
data := value.(cacheData)
|
||||
if data.time <= now {
|
||||
c.cache.Delete(key) // 删除过期缓存
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
}
|
||||
func (c *CacheMemory) Cache(key string, data ...interface{}) *Obj {
|
||||
now := time.Now().Unix()
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (that *CacheMemory) set(key string, value interface{}, time int64) {
|
||||
that.Error.SetError(nil)
|
||||
var data cacheData
|
||||
|
||||
if that.Map == nil {
|
||||
that.Map = Map{}
|
||||
// 随机触发刷新
|
||||
if x := RandX(1, 100000); x > 99950 {
|
||||
c.refreshMap()
|
||||
}
|
||||
|
||||
dd := that.Map[key]
|
||||
|
||||
if dd == nil {
|
||||
data = cacheData{}
|
||||
} else {
|
||||
data = dd.(cacheData)
|
||||
}
|
||||
|
||||
data.time = time
|
||||
data.data = value
|
||||
|
||||
that.Map.Put(key, data)
|
||||
}
|
||||
|
||||
func (that *CacheMemory) delete(key string) {
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
for k, _ := range that.Map {
|
||||
if strings.Index(k, key) != -1 {
|
||||
delete(that.Map, k)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
delete(that.Map, key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheMemory) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
x := RandX(1, 100000)
|
||||
if x > 99950 {
|
||||
that.refreshMap()
|
||||
}
|
||||
if that.mutex == nil {
|
||||
that.mutex = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
reData := &Obj{Data: nil}
|
||||
|
||||
if len(data) == 0 {
|
||||
that.mutex.RLock()
|
||||
reData.Data = that.get(key)
|
||||
that.mutex.RUnlock()
|
||||
return reData
|
||||
// 读操作
|
||||
return c.get(key)
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
that.mutex.Lock()
|
||||
that.delete(key)
|
||||
that.mutex.Unlock()
|
||||
return reData
|
||||
// 删除操作
|
||||
c.delete(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
|
||||
tim = tim + that.TimeOut
|
||||
|
||||
}
|
||||
// 写操作
|
||||
expireAt := now + c.TimeOut
|
||||
if len(data) == 2 {
|
||||
that.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if that.Error.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
if customExpire, ok := data[1].(int64); ok {
|
||||
if customExpire > now {
|
||||
expireAt = customExpire
|
||||
} else {
|
||||
expireAt = now + customExpire
|
||||
}
|
||||
}
|
||||
}
|
||||
that.mutex.Lock()
|
||||
that.set(key, data[0], tim)
|
||||
that.mutex.Unlock()
|
||||
return reData
|
||||
|
||||
c.set(key, data[0], expireAt)
|
||||
return nil
|
||||
}
|
||||
|
||||
Vendored
+89
-69
@@ -4,6 +4,7 @@ import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -14,10 +15,11 @@ type CacheRedis struct {
|
||||
Host string
|
||||
Pwd string
|
||||
Port int64
|
||||
conn redis.Conn
|
||||
pool *redis.Pool
|
||||
tag int64
|
||||
ContextBase
|
||||
*Error
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
func (that *CacheRedis) GetError() *Error {
|
||||
@@ -30,7 +32,7 @@ func (that *CacheRedis) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
// 唯一标志
|
||||
func (that *CacheRedis) GetTag() int64 {
|
||||
|
||||
if that.tag == int64(0) {
|
||||
@@ -39,83 +41,112 @@ func (that *CacheRedis) GetTag() int64 {
|
||||
return that.tag
|
||||
}
|
||||
|
||||
func (that *CacheRedis) reCon() bool {
|
||||
var err error
|
||||
that.conn, err = redis.Dial("tcp", that.Host+":"+ObjToStr(that.Port))
|
||||
if err != nil {
|
||||
that.conn = nil
|
||||
that.Error.SetError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if that.Pwd != "" {
|
||||
_, err = that.conn.Do("AUTH", that.Pwd)
|
||||
if err != nil {
|
||||
that.conn = nil
|
||||
that.Error.SetError(err)
|
||||
return false
|
||||
// initPool 初始化连接池(只执行一次)
|
||||
func (that *CacheRedis) initPool() {
|
||||
that.initOnce.Do(func() {
|
||||
that.pool = &redis.Pool{
|
||||
MaxIdle: 10, // 最大空闲连接数
|
||||
MaxActive: 100, // 最大活跃连接数,0表示无限制
|
||||
IdleTimeout: 5 * time.Minute, // 空闲连接超时时间
|
||||
Wait: true, // 当连接池耗尽时是否等待
|
||||
Dial: func() (redis.Conn, error) {
|
||||
conn, err := redis.Dial("tcp", that.Host+":"+ObjToStr(that.Port),
|
||||
redis.DialConnectTimeout(5*time.Second),
|
||||
redis.DialReadTimeout(3*time.Second),
|
||||
redis.DialWriteTimeout(3*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if that.Pwd != "" {
|
||||
if _, err := conn.Do("AUTH", that.Pwd); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
if time.Since(t) < time.Minute {
|
||||
return nil
|
||||
}
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// getConn 从连接池获取连接
|
||||
func (that *CacheRedis) getConn() redis.Conn {
|
||||
that.initPool()
|
||||
if that.pool == nil {
|
||||
return nil
|
||||
}
|
||||
return that.pool.Get()
|
||||
}
|
||||
|
||||
func (that *CacheRedis) del(key string) {
|
||||
conn := that.getConn()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
del := strings.Index(key, "*")
|
||||
if del != -1 {
|
||||
val, err := redis.Strings(that.conn.Do("KEYS", key))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
that.conn.Send("MULTI")
|
||||
for i, _ := range val {
|
||||
that.conn.Send("DEL", val[i])
|
||||
}
|
||||
that.conn.Do("EXEC")
|
||||
} else {
|
||||
_, err := that.conn.Do("DEL", key)
|
||||
val, err := redis.Strings(conn.Do("KEYS", key))
|
||||
if err != nil {
|
||||
that.Error.SetError(err)
|
||||
return
|
||||
}
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
conn.Send("MULTI")
|
||||
for i := range val {
|
||||
conn.Send("DEL", val[i])
|
||||
}
|
||||
_, err = conn.Do("EXEC")
|
||||
if err != nil {
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
} else {
|
||||
_, err := conn.Do("DEL", key)
|
||||
if err != nil {
|
||||
that.Error.SetError(err)
|
||||
_, err = that.conn.Do("PING")
|
||||
if err != nil {
|
||||
if that.reCon() {
|
||||
_, err = that.conn.Do("DEL", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (that *CacheRedis) set(key string, value string, time int64) {
|
||||
_, err := that.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
if err != nil {
|
||||
// key value ,时间为时间戳
|
||||
func (that *CacheRedis) set(key string, value string, expireSeconds int64) {
|
||||
conn := that.getConn()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err := conn.Do("SET", key, value, "EX", ObjToStr(expireSeconds))
|
||||
if err != nil {
|
||||
that.Error.SetError(err)
|
||||
_, err = that.conn.Do("PING")
|
||||
if err != nil {
|
||||
if that.reCon() {
|
||||
_, err = that.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (that *CacheRedis) get(key string) *Obj {
|
||||
reData := &Obj{}
|
||||
conn := that.getConn()
|
||||
if conn == nil {
|
||||
return reData
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var err error
|
||||
reData.Data, err = redis.String(that.conn.Do("GET", key))
|
||||
reData.Data, err = redis.String(conn.Do("GET", key))
|
||||
if err != nil {
|
||||
reData.Data = nil
|
||||
if !strings.Contains(err.Error(), "nil returned") {
|
||||
that.Error.SetError(err)
|
||||
_, err = that.conn.Do("PING")
|
||||
if err != nil {
|
||||
if that.reCon() {
|
||||
reData.Data, err = redis.String(that.conn.Do("GET", key))
|
||||
}
|
||||
}
|
||||
|
||||
return reData
|
||||
}
|
||||
}
|
||||
return reData
|
||||
@@ -123,19 +154,13 @@ func (that *CacheRedis) get(key string) *Obj {
|
||||
|
||||
func (that *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||
reData := &Obj{}
|
||||
if that.conn == nil {
|
||||
re := that.reCon()
|
||||
if !re {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//查询缓存
|
||||
if len(data) == 0 {
|
||||
|
||||
reData = that.get(key)
|
||||
return reData
|
||||
|
||||
}
|
||||
|
||||
tim := int64(0)
|
||||
//删除缓存
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
@@ -144,21 +169,17 @@ func (that *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||
}
|
||||
//添加缓存
|
||||
if len(data) == 1 {
|
||||
|
||||
if that.TimeOut == 0 {
|
||||
//that.Time = Config.GetInt64("cacheShortTime")
|
||||
}
|
||||
|
||||
tim += that.TimeOut
|
||||
}
|
||||
if len(data) == 2 {
|
||||
that.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if that.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
@@ -166,5 +187,4 @@ func (that *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||
that.set(key, ObjToStr(data[0]), tim)
|
||||
|
||||
return reData
|
||||
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@ type CacheIns interface {
|
||||
Cache(key string, data ...interface{}) *Obj
|
||||
}
|
||||
|
||||
//单条缓存数据
|
||||
// 单条缓存数据
|
||||
type cacheData struct {
|
||||
time int64
|
||||
data interface{}
|
||||
|
||||
+132
-52
@@ -5,12 +5,20 @@ import (
|
||||
)
|
||||
|
||||
var Config = Map{
|
||||
"name": "HoTimeDashBoard",
|
||||
"id": "2f92h3herh23rh2y8",
|
||||
"name": "HoTimeDashBoard",
|
||||
//"id": "2f92h3herh23rh2y8",
|
||||
"label": "HoTime管理平台",
|
||||
|
||||
"stop": Slice{"role", "org"}, //不更新的,同时不允许修改用户自身对应的表数据
|
||||
"labelConfig": Map{
|
||||
"show": "开启",
|
||||
"add": "添加",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"info": "查看详情",
|
||||
"download": "下载清单",
|
||||
},
|
||||
"menus": []Map{
|
||||
{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
|
||||
//{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
|
||||
//{"label": "测试表格", "table": "table", "icon": "el-icon-suitcase"},
|
||||
//{"label": "系统管理", "name": "setting", "icon": "el-icon-setting",
|
||||
// "menus": []Map{
|
||||
@@ -40,6 +48,7 @@ var ColumnDataType = map[string]string{
|
||||
"float": "number",
|
||||
"double": "number",
|
||||
"decimal": "number",
|
||||
"integer": "number", //sqlite3
|
||||
"char": "text",
|
||||
"text": "text",
|
||||
"blob": "text",
|
||||
@@ -50,59 +59,130 @@ var ColumnDataType = map[string]string{
|
||||
}
|
||||
|
||||
type ColumnShow struct {
|
||||
Name string
|
||||
List bool
|
||||
Edit bool
|
||||
Info bool
|
||||
Must bool
|
||||
Name string //名称
|
||||
|
||||
List bool //列表权限
|
||||
Edit bool //新增和编辑权限
|
||||
Info bool //详情权限
|
||||
Must bool //字段全匹配
|
||||
Type string //空字符串表示
|
||||
Strict bool //name严格匹配必须是这个词才行
|
||||
}
|
||||
|
||||
var ColumnNameType = []ColumnShow{
|
||||
//通用
|
||||
{"idcard", false, true, true, false, "", false},
|
||||
{"id", true, false, true, false, "", true},
|
||||
{"parent_id", true, true, true, false, "", true},
|
||||
{"amount", true, true, true, false, "money", true},
|
||||
{"content", false, false, false, false, "textArea", false},
|
||||
{"info", false, true, true, false, "textArea", false},
|
||||
//"sn"{true,true,true,""},
|
||||
{"status", true, true, true, false, "select", false},
|
||||
{"state", true, true, true, false, "select", false},
|
||||
{"sex", true, true, true, false, "select", false},
|
||||
{"delete", false, false, false, false, "", false},
|
||||
var RuleConfig = []Map{
|
||||
{"name": "idcard", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "id", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": true, "type": ""},
|
||||
{"name": "sn", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "parent_ids", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": true, "type": "index"},
|
||||
{"name": "index", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": true, "type": "index"},
|
||||
|
||||
{"lat", false, true, true, false, "", false},
|
||||
{"lng", false, true, true, false, "", false},
|
||||
{"latitude", false, true, true, false, "", false},
|
||||
{"longitude", false, true, true, false, "", false},
|
||||
{"name": "parent_id", "add": true, "list": true, "edit": true, "info": true, "must": false, "true": false, "type": ""},
|
||||
|
||||
{"index", false, false, false, false, "index", false},
|
||||
{"password", false, true, false, false, "password", false},
|
||||
{"pwd", false, true, false, false, "password", false},
|
||||
{"name": "amount", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": true, "type": "money"},
|
||||
|
||||
{"version", false, false, false, false, "", false},
|
||||
{"seq", false, true, true, false, "", false},
|
||||
{"sort", false, true, true, false, "", false},
|
||||
{"note", false, true, true, false, "", false},
|
||||
{"description", false, true, true, false, "", false},
|
||||
{"abstract", false, true, true, false, "", false},
|
||||
{"content", false, true, true, false, "", false},
|
||||
{"address", true, true, true, false, "", false},
|
||||
{"full_name", false, true, true, false, "", false},
|
||||
{"create_time", false, false, true, false, "time", true},
|
||||
{"modify_time", true, false, true, false, "time", true},
|
||||
{"image", false, true, true, false, "image", false},
|
||||
{"img", false, true, true, false, "image", false},
|
||||
{"icon", false, true, true, false, "image", false},
|
||||
{"avatar", false, true, true, false, "image", false},
|
||||
{"file", false, true, true, false, "file", false},
|
||||
{"age", false, true, true, false, "", false},
|
||||
{"email", false, true, true, false, "", false},
|
||||
{"time", true, true, true, false, "time", false},
|
||||
{"level", false, false, true, false, "", false},
|
||||
{"rule", true, true, true, false, "form", false},
|
||||
{"table", true, false, true, false, "table", false},
|
||||
{"table_id", true, false, true, false, "table_id", false},
|
||||
{"name": "info", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "textArea"},
|
||||
|
||||
{"name": "status", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
|
||||
{"name": "state", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
|
||||
{"name": "sex", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
|
||||
|
||||
{"name": "delete", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "lat", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "lng", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "latitude", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "longitude", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "password", "add": true, "list": false, "edit": true, "info": false, "must": false, "strict": false, "type": "password"},
|
||||
{"name": "pwd", "add": true, "list": false, "edit": true, "info": false, "must": false, "strict": false, "type": "password"},
|
||||
|
||||
{"name": "version", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": false, "type": ""},
|
||||
{"name": "seq", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "sort", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "note", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "description", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "abstract", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "content", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "textArea"},
|
||||
|
||||
{"name": "address", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "full_name", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
|
||||
{"name": "create_time", "add": false, "list": false, "edit": false, "info": true, "must": false, "strict": true, "type": "time"},
|
||||
{"name": "modify_time", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": true, "type": "time"},
|
||||
|
||||
{"name": "image", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
|
||||
|
||||
{"name": "img", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
|
||||
{"name": "avatar", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
|
||||
{"name": "icon", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
|
||||
|
||||
{"name": "file", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "file"},
|
||||
|
||||
{"name": "age", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "email", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "time", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "time"},
|
||||
|
||||
{"name": "level", "add": false, "list": false, "edit": false, "info": true, "must": false, "strict": false, "type": ""},
|
||||
{"name": "rule", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "form"},
|
||||
|
||||
{"name": "auth", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "auth"},
|
||||
|
||||
{"name": "table", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": "table"},
|
||||
{"name": "table_id", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": "table_id"},
|
||||
}
|
||||
|
||||
//var ColumnNameType = []ColumnShow{
|
||||
// //通用
|
||||
// {"idcard", false, true, true, false, "", false},
|
||||
// {"id", true, false, true, false, "", true},
|
||||
// {"sn", true, false, true, false, "", false},
|
||||
// {"parent_ids", false, false, false, false, "index", true},
|
||||
// {"parent_id", true, true, true, false, "", true},
|
||||
// {"amount", true, true, true, false, "money", true},
|
||||
// {"info", false, true, true, false, "textArea", false},
|
||||
// //"sn"{true,true,true,""},
|
||||
// {"status", true, true, true, false, "select", false},
|
||||
// {"state", true, true, true, false, "select", false},
|
||||
// {"sex", true, true, true, false, "select", false},
|
||||
// {"delete", false, false, false, false, "", false},
|
||||
//
|
||||
// {"lat", false, true, true, false, "", false},
|
||||
// {"lng", false, true, true, false, "", false},
|
||||
// {"latitude", false, true, true, false, "", false},
|
||||
// {"longitude", false, true, true, false, "", false},
|
||||
//
|
||||
// {"index", false, false, false, false, "index", false},
|
||||
//
|
||||
// {"password", false, true, false, false, "password", false},
|
||||
// {"pwd", false, true, false, false, "password", false},
|
||||
//
|
||||
// {"version", false, false, false, false, "", false},
|
||||
// {"seq", false, true, true, false, "", false},
|
||||
// {"sort", false, true, true, false, "", false},
|
||||
// {"note", false, true, true, false, "", false},
|
||||
// {"description", false, true, true, false, "", false},
|
||||
// {"abstract", false, true, true, false, "", false},
|
||||
// {"content", false, true, true, false, "textArea", false},
|
||||
// {"address", true, true, true, false, "", false},
|
||||
// {"full_name", false, true, true, false, "", false},
|
||||
// {"create_time", false, false, true, false, "time", true},
|
||||
// {"modify_time", true, false, true, false, "time", true},
|
||||
// {"image", false, true, true, false, "image", false},
|
||||
// {"img", false, true, true, false, "image", false},
|
||||
// {"icon", false, true, true, false, "image", false},
|
||||
// {"avatar", false, true, true, false, "image", false},
|
||||
// {"file", false, true, true, false, "file", false},
|
||||
// {"age", false, true, true, false, "", false},
|
||||
// {"email", false, true, true, false, "", false},
|
||||
// {"time", true, true, true, false, "time", false},
|
||||
// {"level", false, false, true, false, "", false},
|
||||
// {"rule", true, true, true, false, "form", false},
|
||||
// {"auth", false, true, true, false, "auth", true},
|
||||
// {"table", true, false, true, false, "table", false},
|
||||
// {"table_id", true, false, true, false, "table_id", false},
|
||||
//}
|
||||
|
||||
+682
-353
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ type ContextBase struct {
|
||||
tag string
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
// 唯一标志
|
||||
func (that *ContextBase) GetTag() string {
|
||||
|
||||
if that.tag == "" {
|
||||
|
||||
+7
-5
@@ -2,26 +2,28 @@ package common
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Error 框架层处理错误
|
||||
type Error struct {
|
||||
Logger *logrus.Logger
|
||||
error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (that *Error) GetError() error {
|
||||
|
||||
that.mu.RLock()
|
||||
defer that.mu.RUnlock()
|
||||
return that.error
|
||||
|
||||
}
|
||||
|
||||
func (that *Error) SetError(err error) {
|
||||
that.mu.Lock()
|
||||
that.error = err
|
||||
that.mu.Unlock()
|
||||
|
||||
if that.Logger != nil && err != nil {
|
||||
//that.Logger=log.GetLog("",false)
|
||||
that.Logger.Warn(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+38
-1
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//安全锁
|
||||
@@ -36,6 +37,42 @@ func StrFirstToUpper(str string) string {
|
||||
return strings.ToUpper(first) + other
|
||||
}
|
||||
|
||||
// 时间转字符串,第二个参数支持1-5对应显示年月日时分秒
|
||||
func Time2Str(t time.Time, qu ...interface{}) string {
|
||||
if t.Unix() < 0 {
|
||||
return ""
|
||||
}
|
||||
tp := 5
|
||||
if len(qu) != 0 {
|
||||
tp = (qu[0]).(int)
|
||||
}
|
||||
|
||||
switch tp {
|
||||
case 1:
|
||||
return t.Format("2006-01")
|
||||
case 2:
|
||||
return t.Format("2006-01-02")
|
||||
case 3:
|
||||
return t.Format("2006-01-02 15")
|
||||
case 4:
|
||||
return t.Format("2006-01-02 15:04")
|
||||
case 5:
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
case 12:
|
||||
return t.Format("01-02")
|
||||
case 14:
|
||||
return t.Format("01-02 15:04")
|
||||
case 15:
|
||||
return t.Format("01-02 15:04:05")
|
||||
case 34:
|
||||
return t.Format("15:04")
|
||||
case 35:
|
||||
return t.Format("15:04:05")
|
||||
}
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
|
||||
}
|
||||
|
||||
// StrLd 相似度计算 ld compares two strings and returns the levenshtein distance between them.
|
||||
func StrLd(s, t string, ignoreCase bool) int {
|
||||
if ignoreCase {
|
||||
@@ -104,7 +141,7 @@ func Substr(str string, start int, length int) string {
|
||||
}
|
||||
|
||||
// IndexLastStr 获取最后出现字符串的下标
|
||||
//return 找不到返回 -1
|
||||
// return 找不到返回 -1
|
||||
func IndexLastStr(str, sep string) int {
|
||||
sepSlice := []rune(sep)
|
||||
strSlice := []rune(str)
|
||||
|
||||
+12
-12
@@ -8,10 +8,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//hotime的常用map
|
||||
// hotime的常用map
|
||||
type Map map[string]interface{}
|
||||
|
||||
//获取string
|
||||
// 获取string
|
||||
func (that Map) GetString(key string, err ...*Error) string {
|
||||
|
||||
if len(err) != 0 {
|
||||
@@ -26,7 +26,7 @@ func (that *Map) Pointer() *Map {
|
||||
return that
|
||||
}
|
||||
|
||||
//增加接口
|
||||
// 增加接口
|
||||
func (that Map) Put(key string, value interface{}) {
|
||||
//if that==nil{
|
||||
// that=Map{}
|
||||
@@ -34,13 +34,13 @@ func (that Map) Put(key string, value interface{}) {
|
||||
that[key] = value
|
||||
}
|
||||
|
||||
//删除接口
|
||||
// 删除接口
|
||||
func (that Map) Delete(key string) {
|
||||
delete(that, key)
|
||||
|
||||
}
|
||||
|
||||
//获取Int
|
||||
// 获取Int
|
||||
func (that Map) GetInt(key string, err ...*Error) int {
|
||||
v := ObjToInt((that)[key], err...)
|
||||
|
||||
@@ -48,35 +48,35 @@ func (that Map) GetInt(key string, err ...*Error) int {
|
||||
|
||||
}
|
||||
|
||||
//获取Int
|
||||
// 获取Int
|
||||
func (that Map) GetInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
// 获取向上取整Int64
|
||||
func (that Map) GetCeilInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
// 获取向上取整Int
|
||||
func (that Map) GetCeilInt(key string, err ...*Error) int {
|
||||
v := ObjToCeilInt((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整float64
|
||||
// 获取向上取整float64
|
||||
func (that Map) GetCeilFloat64(key string, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取Float64
|
||||
// 获取Float64
|
||||
func (that Map) GetFloat64(key string, err ...*Error) float64 {
|
||||
|
||||
v := ObjToFloat64((that)[key], err...)
|
||||
@@ -102,7 +102,7 @@ func (that Map) GetBool(key string, err ...*Error) bool {
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetTime(key string, err ...*Error) time.Time {
|
||||
func (that Map) GetTime(key string, err ...*Error) *time.Time {
|
||||
|
||||
v := ObjToTime((that)[key], err...)
|
||||
return v
|
||||
@@ -149,7 +149,7 @@ func (that Map) Get(key string, err ...*Error) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
//请传递指针过来
|
||||
// 请传递指针过来
|
||||
func (that Map) ToStruct(stct interface{}) {
|
||||
|
||||
data := reflect.ValueOf(stct).Elem()
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ package common
|
||||
|
||||
import "time"
|
||||
|
||||
//对象封装方便取用
|
||||
// 对象封装方便取用
|
||||
type Obj struct {
|
||||
Data interface{}
|
||||
Error
|
||||
@@ -20,7 +20,7 @@ func (that *Obj) ToInt(err ...Error) int {
|
||||
return ObjToInt(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToTime(err ...Error) time.Time {
|
||||
func (that *Obj) ToTime(err ...Error) *time.Time {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func (that *Obj) ToObj() interface{} {
|
||||
return that.Data
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
// 获取向上取整Int64
|
||||
func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
@@ -92,7 +92,7 @@ func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
// 获取向上取整Int
|
||||
func (that *Obj) ToCeilInt(err ...*Error) int {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
|
||||
+47
-24
@@ -5,10 +5,11 @@ import (
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//仅限于hotime.Slice
|
||||
// 仅限于hotime.Slice
|
||||
func ObjToMap(obj interface{}, e ...*Error) Map {
|
||||
var err error
|
||||
var v Map
|
||||
@@ -59,7 +60,7 @@ func ObjToMapArray(obj interface{}, e ...*Error) []Map {
|
||||
return res
|
||||
}
|
||||
|
||||
//仅限于hotime.Slice
|
||||
// 仅限于hotime.Slice
|
||||
func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
||||
var err error
|
||||
var v Slice
|
||||
@@ -97,37 +98,52 @@ func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
||||
return v
|
||||
}
|
||||
|
||||
func ObjToTime(obj interface{}, e ...*Error) time.Time {
|
||||
func ObjToTime(obj interface{}, e ...*Error) *time.Time {
|
||||
|
||||
tInt := ObjToInt64(obj)
|
||||
//字符串类型,只支持标准mysql datetime格式
|
||||
if tInt == 0 {
|
||||
tStr := ObjToStr(obj)
|
||||
|
||||
timeNewStr := ""
|
||||
timeNewStrs := strings.Split(tStr, "-")
|
||||
for _, v := range timeNewStrs {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if len(v) == 1 {
|
||||
v = "0" + v
|
||||
}
|
||||
if timeNewStr == "" {
|
||||
timeNewStr = v
|
||||
continue
|
||||
}
|
||||
timeNewStr = timeNewStr + "-" + v
|
||||
}
|
||||
tStr = timeNewStr
|
||||
if len(tStr) > 18 {
|
||||
t, e := time.Parse("2006-01-02 15:04:05", tStr)
|
||||
if e == nil {
|
||||
return t
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 15 {
|
||||
t, e := time.Parse("2006-01-02 15:04", tStr)
|
||||
if e == nil {
|
||||
return t
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 12 {
|
||||
t, e := time.Parse("2006-01-02 15", tStr)
|
||||
if e == nil {
|
||||
return t
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 9 {
|
||||
t, e := time.Parse("2006-01-02", tStr)
|
||||
if e == nil {
|
||||
return t
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 6 {
|
||||
t, e := time.Parse("2006-01", tStr)
|
||||
if e == nil {
|
||||
return t
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,25 +151,32 @@ func ObjToTime(obj interface{}, e ...*Error) time.Time {
|
||||
|
||||
//纳秒级别
|
||||
if len(ObjToStr(tInt)) > 16 {
|
||||
|
||||
return time.Time{}.Add(time.Nanosecond * time.Duration(tInt))
|
||||
//t := time.Time{}.Add(time.Nanosecond * time.Duration(tInt))
|
||||
t := time.UnixMicro(tInt / 1000)
|
||||
return &t
|
||||
//微秒级别
|
||||
} else if len(ObjToStr(tInt)) > 13 {
|
||||
return time.Time{}.Add(time.Microsecond * time.Duration(tInt))
|
||||
//t := time.Time{}.Add(time.Microsecond * time.Duration(tInt))
|
||||
t := time.UnixMicro(tInt)
|
||||
return &t
|
||||
//毫秒级别
|
||||
} else if len(ObjToStr(tInt)) > 10 {
|
||||
return time.Time{}.Add(time.Millisecond * time.Duration(tInt))
|
||||
//t := time.Time{}.Add(time.Millisecond * time.Duration(tInt))
|
||||
t := time.UnixMilli(tInt)
|
||||
return &t
|
||||
//秒级别
|
||||
} else if len(ObjToStr(tInt)) > 9 {
|
||||
return time.Time{}.Add(time.Second * time.Duration(tInt))
|
||||
//t := time.Time{}.Add(time.Second * time.Duration(tInt))
|
||||
t := time.Unix(tInt, 0)
|
||||
return &t
|
||||
} else if len(ObjToStr(tInt)) > 3 {
|
||||
t, e := time.Parse("2006", ObjToStr(tInt))
|
||||
if e == nil {
|
||||
return t
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
||||
return time.Time{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||
@@ -211,21 +234,21 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||
return v
|
||||
}
|
||||
|
||||
//向上取整
|
||||
// 向上取整
|
||||
func ObjToCeilInt64(obj interface{}, e ...*Error) int64 {
|
||||
f := ObjToCeilFloat64(obj, e...)
|
||||
return ObjToInt64(math.Ceil(f))
|
||||
|
||||
}
|
||||
|
||||
//向上取整
|
||||
// 向上取整
|
||||
func ObjToCeilFloat64(obj interface{}, e ...*Error) float64 {
|
||||
f := ObjToFloat64(obj, e...)
|
||||
return math.Ceil(f)
|
||||
|
||||
}
|
||||
|
||||
//向上取整
|
||||
// 向上取整
|
||||
func ObjToCeilInt(obj interface{}, e ...*Error) int {
|
||||
f := ObjToCeilFloat64(obj, e...)
|
||||
return ObjToInt(f)
|
||||
@@ -337,7 +360,7 @@ func ObjToStr(obj interface{}) string {
|
||||
return str
|
||||
}
|
||||
|
||||
//转换为Map
|
||||
// 转换为Map
|
||||
func StrToMap(string string) Map {
|
||||
data := Map{}
|
||||
data.JsonToMap(string)
|
||||
@@ -345,7 +368,7 @@ func StrToMap(string string) Map {
|
||||
return data
|
||||
}
|
||||
|
||||
//转换为Slice
|
||||
// 转换为Slice
|
||||
func StrToSlice(string string) Slice {
|
||||
|
||||
data := ObjToSlice(string)
|
||||
@@ -353,7 +376,7 @@ func StrToSlice(string string) Slice {
|
||||
return data
|
||||
}
|
||||
|
||||
//字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
// 字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
func StrArrayToJsonStr(a string) string {
|
||||
|
||||
if len(a) > 2 {
|
||||
@@ -371,7 +394,7 @@ func StrArrayToJsonStr(a string) string {
|
||||
return a
|
||||
}
|
||||
|
||||
//字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
// 字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
func JsonStrToStrArray(a string) string {
|
||||
//a = strings.Replace(a, `"`, "", -1)
|
||||
if len(a) != 0 {
|
||||
@@ -381,7 +404,7 @@ func JsonStrToStrArray(a string) string {
|
||||
return "," + a + ","
|
||||
}
|
||||
|
||||
//字符串转int
|
||||
// 字符串转int
|
||||
func StrToInt(s string) (int, error) {
|
||||
i, err := strconv.Atoi(s)
|
||||
return i, err
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ func (that Slice) GetString(key int, err ...*Error) string {
|
||||
return ObjToStr((that)[key])
|
||||
}
|
||||
|
||||
func (that Slice) GetTime(key int, err ...*Error) time.Time {
|
||||
func (that Slice) GetTime(key int, err ...*Error) *time.Time {
|
||||
|
||||
v := ObjToTime((that)[key], err...)
|
||||
return v
|
||||
|
||||
+122
-8
@@ -1,12 +1,17 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/cache"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
@@ -19,10 +24,14 @@ type Context struct {
|
||||
Db *HoTimeDB
|
||||
RespData Map
|
||||
RespFunc func()
|
||||
CacheIns
|
||||
//CacheIns
|
||||
SessionIns
|
||||
DataSize int
|
||||
HandlerStr string //复写请求url
|
||||
|
||||
// 请求参数缓存
|
||||
reqJsonCache Map // JSON Body 缓存
|
||||
reqMu sync.Once // 确保 JSON 只解析一次
|
||||
}
|
||||
|
||||
// Mtd 唯一标志
|
||||
@@ -33,7 +42,7 @@ func (that *Context) Mtd(router [3]string) Map {
|
||||
return d
|
||||
}
|
||||
|
||||
//打印
|
||||
// 打印
|
||||
func (that *Context) Display(statu int, data interface{}) {
|
||||
|
||||
resp := Map{"status": statu}
|
||||
@@ -68,13 +77,26 @@ func (that *Context) View() {
|
||||
}
|
||||
//创建日志
|
||||
if that.Log != nil {
|
||||
that.Log["time"] = time.Now().Unix()
|
||||
that.Log["time"] = time.Now().Format("2006-01-02 15:04")
|
||||
if that.Session("admin_id").Data != nil {
|
||||
that.Log["admin_id"] = that.Session("admin_id").ToCeilInt()
|
||||
}
|
||||
if that.Session("user_id").Data != nil {
|
||||
that.Log["user_id"] = that.Session("user_id").ToCeilInt()
|
||||
}
|
||||
//负载均衡优化
|
||||
ipStr := ""
|
||||
if that.Req.Header.Get("X-Forwarded-For") != "" {
|
||||
ipStr = that.Req.Header.Get("X-Forwarded-For")
|
||||
} else if that.Req.Header.Get("X-Real-IP") != "" {
|
||||
ipStr = that.Req.Header.Get("X-Real-IP")
|
||||
}
|
||||
//负载均衡优化
|
||||
if ipStr == "" {
|
||||
//RemoteAddr := that.Req.RemoteAddr
|
||||
ipStr = Substr(that.Req.RemoteAddr, 0, strings.Index(that.Req.RemoteAddr, ":"))
|
||||
}
|
||||
that.Log["ip"] = ipStr
|
||||
that.Db.Insert("logs", that.Log)
|
||||
}
|
||||
|
||||
@@ -87,5 +109,97 @@ func (that *Context) View() {
|
||||
that.DataSize = len(d)
|
||||
that.RespData = nil
|
||||
that.Resp.Write(d)
|
||||
return
|
||||
}
|
||||
|
||||
// ==================== 请求参数获取方法 ====================
|
||||
|
||||
// ReqParam 获取 URL 查询参数,返回 *Obj 支持链式调用
|
||||
// 用法: that.ReqParam("id").ToInt()
|
||||
func (that *Context) ReqParam(key string) *Obj {
|
||||
v := that.Req.URL.Query().Get(key)
|
||||
if v == "" {
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
|
||||
// ReqForm 获取表单参数,返回 *Obj 支持链式调用
|
||||
// 用法: that.ReqForm("name").ToStr()
|
||||
func (that *Context) ReqForm(key string) *Obj {
|
||||
v := that.Req.FormValue(key)
|
||||
if v == "" {
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
|
||||
// ReqJson 获取 JSON Body 中的字段,返回 *Obj 支持链式调用
|
||||
// 用法: that.ReqJson("data").ToMap()
|
||||
func (that *Context) ReqJson(key string) *Obj {
|
||||
that.parseJsonBody()
|
||||
if that.reqJsonCache == nil {
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
return &Obj{Data: that.reqJsonCache.Get(key)}
|
||||
}
|
||||
|
||||
// parseJsonBody 解析 JSON Body(只解析一次,并发安全)
|
||||
func (that *Context) parseJsonBody() {
|
||||
that.reqMu.Do(func() {
|
||||
if that.Req.Body == nil {
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(that.Req.Body)
|
||||
if err != nil || len(body) == 0 {
|
||||
return
|
||||
}
|
||||
// 恢复 Body 以便后续代码可以再次读取
|
||||
that.Req.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
that.reqJsonCache = ObjToMap(string(body))
|
||||
})
|
||||
}
|
||||
|
||||
// ReqData 统一获取请求参数,优先级: JSON > Form > URL
|
||||
// 用法: that.ReqData("id").ToInt()
|
||||
func (that *Context) ReqData(key string) *Obj {
|
||||
// 1. 优先从 JSON Body 获取
|
||||
that.parseJsonBody()
|
||||
if that.reqJsonCache != nil {
|
||||
if v := that.reqJsonCache.Get(key); v != nil {
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
}
|
||||
// 2. 其次从 Form 获取
|
||||
if v := that.Req.FormValue(key); v != "" {
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
// 3. 最后从 URL 参数获取
|
||||
if v := that.Req.URL.Query().Get(key); v != "" {
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
// ReqFile 获取单个上传文件
|
||||
// 用法: file, header, err := that.ReqFile("avatar")
|
||||
func (that *Context) ReqFile(name string) (multipart.File, *multipart.FileHeader, error) {
|
||||
return that.Req.FormFile(name)
|
||||
}
|
||||
|
||||
// ReqFiles 获取多个同名上传文件(批量上传场景)
|
||||
// 用法: files, err := that.ReqFiles("images")
|
||||
func (that *Context) ReqFiles(name string) ([]*multipart.FileHeader, error) {
|
||||
if that.Req.MultipartForm == nil {
|
||||
if err := that.Req.ParseMultipartForm(32 << 20); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if that.Req.MultipartForm == nil || that.Req.MultipartForm.File == nil {
|
||||
return nil, http.ErrMissingFile
|
||||
}
|
||||
files, ok := that.Req.MultipartForm.File[name]
|
||||
if !ok {
|
||||
return nil, http.ErrMissingFile
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// Count 计数
|
||||
func (that *HoTimeDB) Count(table string, qu ...interface{}) int {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "COUNT(*)")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "COUNT(*)")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["COUNT(*)"])
|
||||
count, _ := StrToInt(res)
|
||||
return count
|
||||
}
|
||||
|
||||
// Sum 求和
|
||||
func (that *HoTimeDB) Sum(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "SUM("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "SUM("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["SUM("+column+")"])
|
||||
sum := ObjToFloat64(res)
|
||||
return sum
|
||||
}
|
||||
|
||||
// Avg 平均值
|
||||
func (that *HoTimeDB) Avg(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "AVG("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "AVG("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["AVG("+column+")"])
|
||||
avg := ObjToFloat64(res)
|
||||
return avg
|
||||
}
|
||||
|
||||
// Max 最大值
|
||||
func (that *HoTimeDB) Max(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "MAX("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "MAX("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["MAX("+column+")"])
|
||||
max := ObjToFloat64(res)
|
||||
return max
|
||||
}
|
||||
|
||||
// Min 最小值
|
||||
func (that *HoTimeDB) Min(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "MIN("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "MIN("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["MIN("+column+")"])
|
||||
min := ObjToFloat64(res)
|
||||
return min
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// backupSave 保存备份
|
||||
// path: 备份文件路径
|
||||
// tt: 表名
|
||||
// code: 备份类型 0=全部, 1=仅数据, 2=仅DDL
|
||||
func (that *HoTimeDB) backupSave(path string, tt string, code int) {
|
||||
fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
defer fd.Close()
|
||||
|
||||
str := "\r\n"
|
||||
if code == 0 || code == 2 {
|
||||
str += that.backupDdl(tt)
|
||||
}
|
||||
|
||||
if code == 0 || code == 1 {
|
||||
str += "insert into `" + tt + "`\r\n\r\n("
|
||||
str += that.backupCol(tt)
|
||||
}
|
||||
|
||||
_, _ = fd.Write([]byte(str))
|
||||
}
|
||||
|
||||
// backupDdl 备份表结构(DDL)
|
||||
func (that *HoTimeDB) backupDdl(tt string) string {
|
||||
data := that.Query("show create table " + tt)
|
||||
if len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
|
||||
}
|
||||
|
||||
// backupCol 备份表数据
|
||||
func (that *HoTimeDB) backupCol(tt string) string {
|
||||
str := ""
|
||||
data := that.Select(tt, "*")
|
||||
|
||||
lthData := len(data)
|
||||
|
||||
if lthData == 0 {
|
||||
return str
|
||||
}
|
||||
|
||||
lthCol := len(data[0])
|
||||
col := make([]string, lthCol)
|
||||
tempLthData := 0
|
||||
|
||||
for k := range data[0] {
|
||||
if tempLthData == lthCol-1 {
|
||||
str += "`" + k + "`) "
|
||||
} else {
|
||||
str += "`" + k + "`,"
|
||||
}
|
||||
col[tempLthData] = k
|
||||
tempLthData++
|
||||
}
|
||||
|
||||
str += " values"
|
||||
|
||||
for j := 0; j < lthData; j++ {
|
||||
for m := 0; m < lthCol; m++ {
|
||||
if m == 0 {
|
||||
str += "("
|
||||
}
|
||||
|
||||
v := "NULL"
|
||||
if data[j][col[m]] != nil {
|
||||
v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'"
|
||||
}
|
||||
|
||||
if m == lthCol-1 {
|
||||
str += v + ") "
|
||||
} else {
|
||||
str += v + ","
|
||||
}
|
||||
}
|
||||
|
||||
if j == lthData-1 {
|
||||
str += ";\r\n\r\n"
|
||||
} else {
|
||||
str += ",\r\n\r\n"
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// HotimeDBBuilder 链式查询构建器
|
||||
type HotimeDBBuilder struct {
|
||||
HoTimeDB *HoTimeDB
|
||||
table string
|
||||
selects []interface{}
|
||||
join Slice
|
||||
where Map
|
||||
lastWhere Map
|
||||
page int
|
||||
pageRow int
|
||||
}
|
||||
|
||||
// Table 创建链式查询构建器
|
||||
func (that *HoTimeDB) Table(table string) *HotimeDBBuilder {
|
||||
return &HotimeDBBuilder{HoTimeDB: that, table: table, where: Map{}}
|
||||
}
|
||||
|
||||
// Get 获取单条记录
|
||||
func (that *HotimeDBBuilder) Get(qu ...interface{}) Map {
|
||||
// 构建参数:根据是否有 JOIN 来决定参数结构
|
||||
var args []interface{}
|
||||
if len(that.join) > 0 {
|
||||
// 有 JOIN 时:join, fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, that.join, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, that.join, "*", that.where)
|
||||
}
|
||||
} else {
|
||||
// 无 JOIN 时:fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, "*", that.where)
|
||||
}
|
||||
}
|
||||
return that.HoTimeDB.Get(that.table, args...)
|
||||
}
|
||||
|
||||
// Count 统计数量
|
||||
func (that *HotimeDBBuilder) Count() int {
|
||||
// 构建参数:根据是否有 JOIN 来决定参数结构
|
||||
if len(that.join) > 0 {
|
||||
return that.HoTimeDB.Count(that.table, that.join, that.where)
|
||||
}
|
||||
return that.HoTimeDB.Count(that.table, that.where)
|
||||
}
|
||||
|
||||
// Page 设置分页
|
||||
func (that *HotimeDBBuilder) Page(page, pageRow int) *HotimeDBBuilder {
|
||||
that.page = page
|
||||
that.pageRow = pageRow
|
||||
return that
|
||||
}
|
||||
|
||||
// Select 查询多条记录
|
||||
func (that *HotimeDBBuilder) Select(qu ...interface{}) []Map {
|
||||
// 构建参数:根据是否有 JOIN 来决定参数结构
|
||||
var args []interface{}
|
||||
if len(that.join) > 0 {
|
||||
// 有 JOIN 时:join, fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, that.join, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, that.join, "*", that.where)
|
||||
}
|
||||
} else {
|
||||
// 无 JOIN 时:fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, "*", that.where)
|
||||
}
|
||||
}
|
||||
|
||||
if that.page != 0 {
|
||||
return that.HoTimeDB.Page(that.page, that.pageRow).PageSelect(that.table, args...)
|
||||
}
|
||||
return that.HoTimeDB.Select(that.table, args...)
|
||||
}
|
||||
|
||||
// Update 更新记录
|
||||
func (that *HotimeDBBuilder) Update(qu ...interface{}) int64 {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return 0
|
||||
}
|
||||
data := Map{}
|
||||
if lth == 1 {
|
||||
data = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
for k := 1; k < lth; k++ {
|
||||
data[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
return that.HoTimeDB.Update(that.table, data, that.where)
|
||||
}
|
||||
|
||||
// Delete 删除记录
|
||||
func (that *HotimeDBBuilder) Delete() int64 {
|
||||
return that.HoTimeDB.Delete(that.table, that.where)
|
||||
}
|
||||
|
||||
// LeftJoin 左连接
|
||||
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[>]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// RightJoin 右连接
|
||||
func (that *HotimeDBBuilder) RightJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[<]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// InnerJoin 内连接
|
||||
func (that *HotimeDBBuilder) InnerJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[><]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// FullJoin 全连接
|
||||
func (that *HotimeDBBuilder) FullJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[<>]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// Join 通用连接
|
||||
func (that *HotimeDBBuilder) Join(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
data := Map{}
|
||||
if lth == 1 {
|
||||
data = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
for k := 1; k < lth; k++ {
|
||||
data[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if that.join == nil {
|
||||
that.join = Slice{}
|
||||
}
|
||||
if data == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
that.join = append(that.join, data)
|
||||
return that
|
||||
}
|
||||
|
||||
// And 添加 AND 条件
|
||||
func (that *HotimeDBBuilder) And(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var where Map
|
||||
if lth == 1 {
|
||||
where = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
where = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
where[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if where == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
if that.lastWhere != nil {
|
||||
that.lastWhere["AND"] = where
|
||||
that.lastWhere = where
|
||||
return that
|
||||
}
|
||||
that.lastWhere = where
|
||||
that.where = Map{"AND": where}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// Or 添加 OR 条件
|
||||
func (that *HotimeDBBuilder) Or(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var where Map
|
||||
if lth == 1 {
|
||||
where = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
where = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
where[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
if where == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
if that.lastWhere != nil {
|
||||
that.lastWhere["OR"] = where
|
||||
that.lastWhere = where
|
||||
return that
|
||||
}
|
||||
that.lastWhere = where
|
||||
that.where = Map{"Or": where}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// Where 设置 WHERE 条件
|
||||
func (that *HotimeDBBuilder) Where(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var where Map
|
||||
if lth == 1 {
|
||||
where = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
where = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
where[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if where == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
if that.lastWhere != nil {
|
||||
that.lastWhere["AND"] = where
|
||||
that.lastWhere = where
|
||||
return that
|
||||
}
|
||||
that.lastWhere = where
|
||||
that.where = Map{"AND": that.lastWhere}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// From 设置表名
|
||||
func (that *HotimeDBBuilder) From(table string) *HotimeDBBuilder {
|
||||
that.table = table
|
||||
return that
|
||||
}
|
||||
|
||||
// Order 设置排序
|
||||
func (that *HotimeDBBuilder) Order(qu ...interface{}) *HotimeDBBuilder {
|
||||
that.where["ORDER"] = ObjToSlice(qu)
|
||||
return that
|
||||
}
|
||||
|
||||
// Limit 设置限制
|
||||
func (that *HotimeDBBuilder) Limit(qu ...interface{}) *HotimeDBBuilder {
|
||||
that.where["LIMIT"] = ObjToSlice(qu)
|
||||
return that
|
||||
}
|
||||
|
||||
// Group 设置分组
|
||||
func (that *HotimeDBBuilder) Group(qu ...interface{}) *HotimeDBBuilder {
|
||||
that.where["GROUP"] = ObjToSlice(qu)
|
||||
return that
|
||||
}
|
||||
|
||||
// Having 设置 HAVING 条件
|
||||
func (that *HotimeDBBuilder) Having(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var having Map
|
||||
if lth == 1 {
|
||||
having = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
having = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
having[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
that.where["HAVING"] = having
|
||||
return that
|
||||
}
|
||||
|
||||
// Offset 设置偏移量
|
||||
func (that *HotimeDBBuilder) Offset(offset int) *HotimeDBBuilder {
|
||||
that.where["OFFSET"] = offset
|
||||
return that
|
||||
}
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// Page 设置分页参数
|
||||
// page: 页码(从1开始)
|
||||
// pageRow: 每页数量
|
||||
func (that *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageRow < 1 {
|
||||
pageRow = 10
|
||||
}
|
||||
offset := (page - 1) * pageRow
|
||||
|
||||
that.limitMu.Lock()
|
||||
that.limit = Slice{offset, pageRow}
|
||||
that.limitMu.Unlock()
|
||||
return that
|
||||
}
|
||||
|
||||
// PageSelect 分页查询
|
||||
func (that *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
|
||||
that.limitMu.Lock()
|
||||
limit := that.limit
|
||||
that.limit = nil // 使用后清空,避免影响下次调用
|
||||
that.limitMu.Unlock()
|
||||
|
||||
if limit == nil {
|
||||
return that.Select(table, qu...)
|
||||
}
|
||||
|
||||
// 根据参数数量处理 LIMIT 注入
|
||||
switch len(qu) {
|
||||
case 0:
|
||||
// PageSelect("user") -> 只有表名,添加 LIMIT
|
||||
qu = append(qu, "*", Map{"LIMIT": limit})
|
||||
case 1:
|
||||
// PageSelect("user", "*") 或 PageSelect("user", Map{...})
|
||||
if reflect.ValueOf(qu[0]).Kind() == reflect.Map {
|
||||
// 是 where 条件
|
||||
temp := DeepCopyMap(qu[0]).(Map)
|
||||
temp["LIMIT"] = limit
|
||||
qu[0] = temp
|
||||
} else {
|
||||
// 是字段选择
|
||||
qu = append(qu, Map{"LIMIT": limit})
|
||||
}
|
||||
case 2:
|
||||
// PageSelect("user", "*", Map{...}) 或 PageSelect("user", joinSlice, "*")
|
||||
if reflect.ValueOf(qu[1]).Kind() == reflect.Map {
|
||||
temp := DeepCopyMap(qu[1]).(Map)
|
||||
temp["LIMIT"] = limit
|
||||
qu[1] = temp
|
||||
} else {
|
||||
// join 模式,需要追加 where
|
||||
qu = append(qu, Map{"LIMIT": limit})
|
||||
}
|
||||
case 3:
|
||||
// PageSelect("user", joinSlice, "*", Map{...})
|
||||
temp := DeepCopyMap(qu[2]).(Map)
|
||||
temp["LIMIT"] = limit
|
||||
qu[2] = temp
|
||||
}
|
||||
|
||||
return that.Select(table, qu...)
|
||||
}
|
||||
|
||||
// Select 查询多条记录
|
||||
func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map {
|
||||
query := "SELECT"
|
||||
where := Map{}
|
||||
qs := make([]interface{}, 0)
|
||||
intQs, intWhere := 0, 1
|
||||
join := false
|
||||
|
||||
if len(qu) == 3 {
|
||||
intQs = 1
|
||||
intWhere = 2
|
||||
join = true
|
||||
}
|
||||
|
||||
processor := that.GetProcessor()
|
||||
|
||||
if len(qu) > 0 {
|
||||
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
|
||||
// 字段列表字符串,使用处理器处理 table.column 格式
|
||||
fieldStr := qu[intQs].(string)
|
||||
if fieldStr != "*" {
|
||||
fieldStr = processor.ProcessFieldList(fieldStr)
|
||||
}
|
||||
query += " " + fieldStr
|
||||
} else {
|
||||
data := ObjToSlice(qu[intQs])
|
||||
for i := 0; i < len(data); i++ {
|
||||
k := data.GetString(i)
|
||||
if strings.Contains(k, " AS ") || strings.Contains(k, ".") {
|
||||
// 处理 table.column 格式
|
||||
query += " " + processor.ProcessFieldList(k) + " "
|
||||
} else {
|
||||
// 单独的列名
|
||||
query += " " + processor.ProcessColumnNoPrefix(k) + " "
|
||||
}
|
||||
|
||||
if i+1 != len(data) {
|
||||
query = query + ", "
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query += " *"
|
||||
}
|
||||
|
||||
// 处理表名(添加前缀和正确的引号)
|
||||
query += " FROM " + processor.ProcessTableName(table) + " "
|
||||
|
||||
if join {
|
||||
query += that.buildJoin(qu[0])
|
||||
}
|
||||
|
||||
if len(qu) > 1 {
|
||||
where = qu[intWhere].(Map)
|
||||
}
|
||||
|
||||
temp, resWhere := that.where(where)
|
||||
|
||||
query += temp + ";"
|
||||
qs = append(qs, resWhere...)
|
||||
md5 := that.md5(query, qs...)
|
||||
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
// 如果缓存有则从缓存取
|
||||
cacheData := that.HoTimeCache.Db(table + ":" + md5)
|
||||
if cacheData != nil && cacheData.Data != nil {
|
||||
return cacheData.ToMapArray()
|
||||
}
|
||||
}
|
||||
|
||||
// 无缓存则数据库取
|
||||
res := that.Query(query, qs...)
|
||||
|
||||
if res == nil {
|
||||
res = []Map{}
|
||||
}
|
||||
|
||||
// 缓存
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+":"+md5, res)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// buildJoin 构建 JOIN 语句
|
||||
func (that *HoTimeDB) buildJoin(joinData interface{}) string {
|
||||
query := ""
|
||||
var testQu = []string{}
|
||||
testQuData := Map{}
|
||||
processor := that.GetProcessor()
|
||||
|
||||
if reflect.ValueOf(joinData).Type().String() == "common.Map" {
|
||||
testQuData = joinData.(Map)
|
||||
for key := range testQuData {
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.ValueOf(joinData).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(joinData).Type().String(), "[]") {
|
||||
qu0 := ObjToSlice(joinData)
|
||||
for key := range qu0 {
|
||||
v := qu0.GetMap(key)
|
||||
for k1, v1 := range v {
|
||||
testQu = append(testQu, k1)
|
||||
testQuData[k1] = v1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(testQu)
|
||||
|
||||
for _, k := range testQu {
|
||||
v := testQuData[k]
|
||||
switch Substr(k, 0, 3) {
|
||||
case "[>]":
|
||||
func() {
|
||||
table := Substr(k, 3, len(k)-3)
|
||||
// 处理表名(添加前缀和正确的引号)
|
||||
table = processor.ProcessTableName(table)
|
||||
// 处理 ON 条件中的 table.column
|
||||
onCondition := processor.ProcessConditionString(v.(string))
|
||||
query += " LEFT JOIN " + table + " ON " + onCondition + " "
|
||||
}()
|
||||
case "[<]":
|
||||
func() {
|
||||
table := Substr(k, 3, len(k)-3)
|
||||
table = processor.ProcessTableName(table)
|
||||
onCondition := processor.ProcessConditionString(v.(string))
|
||||
query += " RIGHT JOIN " + table + " ON " + onCondition + " "
|
||||
}()
|
||||
}
|
||||
switch Substr(k, 0, 4) {
|
||||
case "[<>]":
|
||||
func() {
|
||||
table := Substr(k, 4, len(k)-4)
|
||||
table = processor.ProcessTableName(table)
|
||||
onCondition := processor.ProcessConditionString(v.(string))
|
||||
query += " FULL JOIN " + table + " ON " + onCondition + " "
|
||||
}()
|
||||
case "[><]":
|
||||
func() {
|
||||
table := Substr(k, 4, len(k)-4)
|
||||
table = processor.ProcessTableName(table)
|
||||
onCondition := processor.ProcessConditionString(v.(string))
|
||||
query += " INNER JOIN " + table + " ON " + onCondition + " "
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// Get 获取单条记录
|
||||
func (that *HoTimeDB) Get(table string, qu ...interface{}) Map {
|
||||
if len(qu) == 0 {
|
||||
// 没有参数时,添加默认字段和 LIMIT
|
||||
qu = append(qu, "*", Map{"LIMIT": 1})
|
||||
} else if len(qu) == 1 {
|
||||
qu = append(qu, Map{"LIMIT": 1})
|
||||
} else if len(qu) == 2 {
|
||||
temp := qu[1].(Map)
|
||||
temp["LIMIT"] = 1
|
||||
qu[1] = temp
|
||||
} else if len(qu) == 3 {
|
||||
temp := qu[2].(Map)
|
||||
temp["LIMIT"] = 1
|
||||
qu[2] = temp
|
||||
}
|
||||
|
||||
data := that.Select(table, qu...)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data[0]
|
||||
}
|
||||
|
||||
// Insert 插入新数据
|
||||
func (that *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
|
||||
values := make([]interface{}, 0)
|
||||
queryString := " ("
|
||||
valueString := " ("
|
||||
processor := that.GetProcessor()
|
||||
|
||||
lens := len(data)
|
||||
tempLen := 0
|
||||
|
||||
for k, v := range data {
|
||||
tempLen++
|
||||
|
||||
vstr := "?"
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
vstr = ObjToStr(v)
|
||||
if tempLen < lens {
|
||||
queryString += processor.ProcessColumnNoPrefix(k) + ","
|
||||
valueString += vstr + ","
|
||||
} else {
|
||||
queryString += processor.ProcessColumnNoPrefix(k) + ") "
|
||||
valueString += vstr + ");"
|
||||
}
|
||||
} else {
|
||||
values = append(values, v)
|
||||
if tempLen < lens {
|
||||
queryString += processor.ProcessColumnNoPrefix(k) + ","
|
||||
valueString += "?,"
|
||||
} else {
|
||||
queryString += processor.ProcessColumnNoPrefix(k) + ") "
|
||||
valueString += "?);"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query := "INSERT INTO " + processor.ProcessTableName(table) + " " + queryString + "VALUES" + valueString
|
||||
|
||||
res, err := that.Exec(query, values...)
|
||||
|
||||
id := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
id1, err := res.LastInsertId()
|
||||
that.LastErr.SetError(err)
|
||||
id = id1
|
||||
}
|
||||
|
||||
// 如果插入成功,删除缓存
|
||||
if id != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Inserts 批量插入数据
|
||||
// table: 表名
|
||||
// dataList: 数据列表,每个元素是一个 Map
|
||||
// 返回受影响的行数
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// affected := db.Inserts("user", []Map{
|
||||
// {"name": "张三", "age": 25, "email": "zhang@example.com"},
|
||||
// {"name": "李四", "age": 30, "email": "li@example.com"},
|
||||
// {"name": "王五", "age": 28, "email": "wang@example.com"},
|
||||
// })
|
||||
func (that *HoTimeDB) Inserts(table string, dataList []Map) int64 {
|
||||
if len(dataList) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 从第一条数据提取所有列名(确保顺序一致)
|
||||
columns := make([]string, 0)
|
||||
rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值
|
||||
|
||||
for k := range dataList[0] {
|
||||
realKey := k
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
realKey = strings.Replace(k, "[#]", "", -1)
|
||||
rawValues[realKey] = ObjToStr(dataList[0][k])
|
||||
}
|
||||
columns = append(columns, realKey)
|
||||
}
|
||||
|
||||
// 排序列名以确保一致性
|
||||
sort.Strings(columns)
|
||||
|
||||
processor := that.GetProcessor()
|
||||
|
||||
// 构建列名部分
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = processor.ProcessColumnNoPrefix(col)
|
||||
}
|
||||
colStr := strings.Join(quotedCols, ", ")
|
||||
|
||||
// 构建每行的占位符和值
|
||||
placeholders := make([]string, len(dataList))
|
||||
values := make([]interface{}, 0, len(dataList)*len(columns))
|
||||
|
||||
for i, data := range dataList {
|
||||
rowPlaceholders := make([]string, len(columns))
|
||||
for j, col := range columns {
|
||||
// 检查是否有 [#] 标记
|
||||
rawKey := col + "[#]"
|
||||
if rawVal, ok := data[rawKey]; ok {
|
||||
// 直接 SQL 表达式
|
||||
rowPlaceholders[j] = ObjToStr(rawVal)
|
||||
} else if _, isRaw := rawValues[col]; isRaw && i == 0 {
|
||||
// 第一条数据中的 [#] 标记
|
||||
rowPlaceholders[j] = rawValues[col]
|
||||
} else if val, ok := data[col]; ok {
|
||||
// 普通值
|
||||
rowPlaceholders[j] = "?"
|
||||
values = append(values, val)
|
||||
} else {
|
||||
// 字段不存在,使用 NULL
|
||||
rowPlaceholders[j] = "NULL"
|
||||
}
|
||||
}
|
||||
placeholders[i] = "(" + strings.Join(rowPlaceholders, ", ") + ")"
|
||||
}
|
||||
|
||||
query := "INSERT INTO " + processor.ProcessTableName(table) + " (" + colStr + ") VALUES " + strings.Join(placeholders, ", ")
|
||||
|
||||
res, err := that.Exec(query, values...)
|
||||
|
||||
rows64 := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows64, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 如果插入成功,删除缓存
|
||||
if rows64 != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows64
|
||||
}
|
||||
|
||||
// Upsert 插入或更新数据
|
||||
// table: 表名
|
||||
// data: 要插入的数据
|
||||
// uniqueKeys: 唯一键字段(用于冲突检测),支持 Slice{"id"} 或 Slice{"col1", "col2"}
|
||||
// updateColumns: 冲突时要更新的字段(如果为空,则更新所有非唯一键字段)
|
||||
// 返回受影响的行数
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// affected := db.Upsert("user",
|
||||
// Map{"id": 1, "name": "张三", "email": "zhang@example.com"},
|
||||
// Slice{"id"}, // 唯一键
|
||||
// Slice{"name", "email"}, // 冲突时更新的字段
|
||||
// )
|
||||
func (that *HoTimeDB) Upsert(table string, data Map, uniqueKeys Slice, updateColumns ...interface{}) int64 {
|
||||
if len(data) == 0 || len(uniqueKeys) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 转换 uniqueKeys 为 []string
|
||||
uniqueKeyStrs := make([]string, len(uniqueKeys))
|
||||
for i, uk := range uniqueKeys {
|
||||
uniqueKeyStrs[i] = ObjToStr(uk)
|
||||
}
|
||||
|
||||
// 转换 updateColumns 为 []string
|
||||
var updateColumnStrs []string
|
||||
if len(updateColumns) > 0 {
|
||||
// 支持两种调用方式:Upsert(table, data, Slice{"id"}, Slice{"name"}) 或 Upsert(table, data, Slice{"id"}, "name", "email")
|
||||
if slice, ok := updateColumns[0].(Slice); ok {
|
||||
updateColumnStrs = make([]string, len(slice))
|
||||
for i, col := range slice {
|
||||
updateColumnStrs[i] = ObjToStr(col)
|
||||
}
|
||||
} else {
|
||||
updateColumnStrs = make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
updateColumnStrs[i] = ObjToStr(col)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收集列和值
|
||||
columns := make([]string, 0, len(data))
|
||||
values := make([]interface{}, 0, len(data))
|
||||
rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值
|
||||
|
||||
for k, v := range data {
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
realKey := strings.Replace(k, "[#]", "", -1)
|
||||
columns = append(columns, realKey)
|
||||
rawValues[realKey] = ObjToStr(v)
|
||||
} else {
|
||||
columns = append(columns, k)
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定更新字段,则更新所有非唯一键字段
|
||||
if len(updateColumnStrs) == 0 {
|
||||
uniqueKeySet := make(map[string]bool)
|
||||
for _, uk := range uniqueKeyStrs {
|
||||
uniqueKeySet[uk] = true
|
||||
}
|
||||
for _, col := range columns {
|
||||
if !uniqueKeySet[col] {
|
||||
updateColumnStrs = append(updateColumnStrs, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建 SQL
|
||||
var query string
|
||||
dbType := that.Type
|
||||
if dbType == "" {
|
||||
dbType = "mysql"
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
case "postgres", "postgresql":
|
||||
query = that.buildPostgresUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
|
||||
case "sqlite3", "sqlite":
|
||||
query = that.buildSQLiteUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
|
||||
default: // mysql
|
||||
query = that.buildMySQLUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
|
||||
}
|
||||
|
||||
res, err := that.Exec(query, values...)
|
||||
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
if rows != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
// buildMySQLUpsert 构建 MySQL 的 Upsert 语句
|
||||
func (that *HoTimeDB) buildMySQLUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2)
|
||||
processor := that.GetProcessor()
|
||||
|
||||
quotedCols := make([]string, len(columns))
|
||||
valueParts := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = processor.ProcessColumnNoPrefix(col)
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
valueParts[i] = raw
|
||||
} else {
|
||||
valueParts[i] = "?"
|
||||
}
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
quotedCol := processor.ProcessColumnNoPrefix(col)
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
updateParts[i] = quotedCol + " = " + raw
|
||||
} else {
|
||||
updateParts[i] = quotedCol + " = VALUES(" + quotedCol + ")"
|
||||
}
|
||||
}
|
||||
|
||||
return "INSERT INTO " + processor.ProcessTableName(table) + " (" + strings.Join(quotedCols, ", ") +
|
||||
") VALUES (" + strings.Join(valueParts, ", ") +
|
||||
") ON DUPLICATE KEY UPDATE " + strings.Join(updateParts, ", ")
|
||||
}
|
||||
|
||||
// buildPostgresUpsert 构建 PostgreSQL 的 Upsert 语句
|
||||
func (that *HoTimeDB) buildPostgresUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES ($1, $2)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = EXCLUDED.col1
|
||||
processor := that.GetProcessor()
|
||||
dialect := that.GetDialect()
|
||||
|
||||
quotedCols := make([]string, len(columns))
|
||||
valueParts := make([]string, len(columns))
|
||||
paramIndex := 1
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = dialect.QuoteIdentifier(col)
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
valueParts[i] = raw
|
||||
} else {
|
||||
valueParts[i] = "$" + ObjToStr(paramIndex)
|
||||
paramIndex++
|
||||
}
|
||||
}
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = dialect.QuoteIdentifier(key)
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
quotedCol := dialect.QuoteIdentifier(col)
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
updateParts[i] = quotedCol + " = " + raw
|
||||
} else {
|
||||
updateParts[i] = quotedCol + " = EXCLUDED." + quotedCol
|
||||
}
|
||||
}
|
||||
|
||||
return "INSERT INTO " + processor.ProcessTableName(table) + " (" + strings.Join(quotedCols, ", ") +
|
||||
") VALUES (" + strings.Join(valueParts, ", ") +
|
||||
") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") +
|
||||
") DO UPDATE SET " + strings.Join(updateParts, ", ")
|
||||
}
|
||||
|
||||
// buildSQLiteUpsert 构建 SQLite 的 Upsert 语句
|
||||
func (that *HoTimeDB) buildSQLiteUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = excluded.col1
|
||||
processor := that.GetProcessor()
|
||||
dialect := that.GetDialect()
|
||||
|
||||
quotedCols := make([]string, len(columns))
|
||||
valueParts := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = dialect.QuoteIdentifier(col)
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
valueParts[i] = raw
|
||||
} else {
|
||||
valueParts[i] = "?"
|
||||
}
|
||||
}
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = dialect.QuoteIdentifier(key)
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
quotedCol := dialect.QuoteIdentifier(col)
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
updateParts[i] = quotedCol + " = " + raw
|
||||
} else {
|
||||
updateParts[i] = quotedCol + " = excluded." + quotedCol
|
||||
}
|
||||
}
|
||||
|
||||
return "INSERT INTO " + processor.ProcessTableName(table) + " (" + strings.Join(quotedCols, ", ") +
|
||||
") VALUES (" + strings.Join(valueParts, ", ") +
|
||||
") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") +
|
||||
") DO UPDATE SET " + strings.Join(updateParts, ", ")
|
||||
}
|
||||
|
||||
// Update 更新数据
|
||||
func (that *HoTimeDB) Update(table string, data Map, where Map) int64 {
|
||||
processor := that.GetProcessor()
|
||||
query := "UPDATE " + processor.ProcessTableName(table) + " SET "
|
||||
qs := make([]interface{}, 0)
|
||||
tp := len(data)
|
||||
|
||||
for k, v := range data {
|
||||
vstr := "?"
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
vstr = ObjToStr(v)
|
||||
} else {
|
||||
qs = append(qs, v)
|
||||
}
|
||||
query += processor.ProcessColumnNoPrefix(k) + "=" + vstr + " "
|
||||
if tp--; tp != 0 {
|
||||
query += ", "
|
||||
}
|
||||
}
|
||||
|
||||
temp, resWhere := that.where(where)
|
||||
|
||||
query += temp + ";"
|
||||
qs = append(qs, resWhere...)
|
||||
|
||||
res, err := that.Exec(query, qs...)
|
||||
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 如果更新成功,则删除缓存
|
||||
if rows != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
// Delete 删除数据
|
||||
func (that *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
|
||||
processor := that.GetProcessor()
|
||||
query := "DELETE FROM " + processor.ProcessTableName(table) + " "
|
||||
|
||||
temp, resWhere := that.where(data)
|
||||
query += temp + ";"
|
||||
|
||||
res, err := that.Exec(query, resWhere...)
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 如果删除成功,删除对应缓存
|
||||
if rows != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/cache"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HoTimeDB 数据库操作核心结构体
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
ContextBase
|
||||
DBName string
|
||||
*cache.HoTimeCache
|
||||
Log *logrus.Logger
|
||||
Type string // 数据库类型: mysql, sqlite3, postgres
|
||||
Prefix string
|
||||
LastQuery string
|
||||
LastData []interface{}
|
||||
ConnectFunc func(err ...*Error) (*sql.DB, *sql.DB)
|
||||
LastErr *Error
|
||||
limit Slice
|
||||
*sql.Tx //事务对象
|
||||
SlaveDB *sql.DB // 从数据库
|
||||
Mode int // mode为0生产模式,1为测试模式,2为开发模式
|
||||
mu sync.RWMutex
|
||||
limitMu sync.Mutex
|
||||
Dialect Dialect // 数据库方言适配器
|
||||
}
|
||||
|
||||
// SetConnect 设置数据库配置连接
|
||||
func (that *HoTimeDB) SetConnect(connect func(err ...*Error) (master, slave *sql.DB), err ...*Error) {
|
||||
that.ConnectFunc = connect
|
||||
_ = that.InitDb(err...)
|
||||
}
|
||||
|
||||
// InitDb 初始化数据库连接
|
||||
func (that *HoTimeDB) InitDb(err ...*Error) *Error {
|
||||
if len(err) != 0 {
|
||||
that.LastErr = err[0]
|
||||
}
|
||||
that.DB, that.SlaveDB = that.ConnectFunc(that.LastErr)
|
||||
if that.DB == nil {
|
||||
return that.LastErr
|
||||
}
|
||||
e := that.DB.Ping()
|
||||
|
||||
that.LastErr.SetError(e)
|
||||
|
||||
if that.SlaveDB != nil {
|
||||
e := that.SlaveDB.Ping()
|
||||
that.LastErr.SetError(e)
|
||||
}
|
||||
|
||||
// 根据数据库类型初始化方言适配器
|
||||
if that.Dialect == nil {
|
||||
that.initDialect()
|
||||
}
|
||||
|
||||
return that.LastErr
|
||||
}
|
||||
|
||||
// initDialect 根据数据库类型初始化方言
|
||||
func (that *HoTimeDB) initDialect() {
|
||||
switch that.Type {
|
||||
case "postgres", "postgresql":
|
||||
that.Dialect = &PostgreSQLDialect{}
|
||||
case "sqlite3", "sqlite":
|
||||
that.Dialect = &SQLiteDialect{}
|
||||
default:
|
||||
that.Dialect = &MySQLDialect{}
|
||||
}
|
||||
}
|
||||
|
||||
// GetDialect 获取当前方言适配器
|
||||
func (that *HoTimeDB) GetDialect() Dialect {
|
||||
if that.Dialect == nil {
|
||||
that.initDialect()
|
||||
}
|
||||
return that.Dialect
|
||||
}
|
||||
|
||||
// SetDialect 设置方言适配器
|
||||
func (that *HoTimeDB) SetDialect(dialect Dialect) {
|
||||
that.Dialect = dialect
|
||||
}
|
||||
|
||||
// GetType 获取数据库类型
|
||||
func (that *HoTimeDB) GetType() string {
|
||||
return that.Type
|
||||
}
|
||||
|
||||
// GetPrefix 获取表前缀
|
||||
func (that *HoTimeDB) GetPrefix() string {
|
||||
return that.Prefix
|
||||
}
|
||||
|
||||
// GetProcessor 获取标识符处理器
|
||||
// 用于处理表名、字段名的前缀添加和引号转换
|
||||
func (that *HoTimeDB) GetProcessor() *IdentifierProcessor {
|
||||
return NewIdentifierProcessor(that.GetDialect(), that.Prefix)
|
||||
}
|
||||
|
||||
// T 辅助方法:获取带前缀和引号的表名
|
||||
// 用于手动构建 SQL 时使用
|
||||
// 示例: db.T("order") 返回 "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL)
|
||||
func (that *HoTimeDB) T(table string) string {
|
||||
return that.GetProcessor().ProcessTableName(table)
|
||||
}
|
||||
|
||||
// C 辅助方法:获取带前缀和引号的 table.column
|
||||
// 支持两种调用方式:
|
||||
// - db.C("order", "name") 返回 "`app_order`.`name`"
|
||||
// - db.C("order.name") 返回 "`app_order`.`name`"
|
||||
func (that *HoTimeDB) C(args ...string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(args) == 1 {
|
||||
return that.GetProcessor().ProcessColumn(args[0])
|
||||
}
|
||||
// 两个参数: table, column
|
||||
dialect := that.GetDialect()
|
||||
table := args[0]
|
||||
column := args[1]
|
||||
// 去除已有引号
|
||||
table = trimQuotes(table)
|
||||
column = trimQuotes(column)
|
||||
return dialect.QuoteIdentifier(that.Prefix+table) + "." + dialect.QuoteIdentifier(column)
|
||||
}
|
||||
|
||||
// trimQuotes 去除字符串两端的引号
|
||||
func trimQuotes(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) >= 2 {
|
||||
if (s[0] == '`' && s[len(s)-1] == '`') || (s[0] == '"' && s[len(s)-1] == '"') {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dialect 数据库方言接口
|
||||
// 用于处理不同数据库之间的语法差异
|
||||
type Dialect interface {
|
||||
// Quote 对表名/字段名添加引号
|
||||
// MySQL 使用反引号 `name`
|
||||
// PostgreSQL 使用双引号 "name"
|
||||
// SQLite 使用双引号或方括号 "name" 或 [name]
|
||||
Quote(name string) string
|
||||
|
||||
// QuoteIdentifier 处理单个标识符(去除已有引号,添加正确引号)
|
||||
// 输入可能带有反引号或双引号,会先去除再添加正确格式
|
||||
QuoteIdentifier(name string) string
|
||||
|
||||
// QuoteChar 返回引号字符
|
||||
// MySQL: `
|
||||
// PostgreSQL/SQLite: "
|
||||
QuoteChar() string
|
||||
|
||||
// Placeholder 生成占位符
|
||||
// MySQL/SQLite 使用 ?
|
||||
// PostgreSQL 使用 $1, $2, $3...
|
||||
Placeholder(index int) string
|
||||
|
||||
// Placeholders 生成多个占位符,用逗号分隔
|
||||
Placeholders(count int, startIndex int) string
|
||||
|
||||
// SupportsLastInsertId 是否支持 LastInsertId
|
||||
// PostgreSQL 不支持,需要使用 RETURNING
|
||||
SupportsLastInsertId() bool
|
||||
|
||||
// ReturningClause 生成 RETURNING 子句(用于 PostgreSQL)
|
||||
ReturningClause(column string) string
|
||||
|
||||
// UpsertSQL 生成 Upsert 语句
|
||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE ...
|
||||
// PostgreSQL: INSERT ... ON CONFLICT ... DO UPDATE SET ...
|
||||
// SQLite: INSERT OR REPLACE / INSERT ... ON CONFLICT ...
|
||||
UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string
|
||||
|
||||
// GetName 获取方言名称
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// MySQLDialect MySQL 方言实现
|
||||
type MySQLDialect struct{}
|
||||
|
||||
func (d *MySQLDialect) GetName() string {
|
||||
return "mysql"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Quote(name string) string {
|
||||
// 如果已经包含点号(表.字段)或空格(别名),不添加引号
|
||||
if strings.Contains(name, ".") || strings.Contains(name, " ") {
|
||||
return name
|
||||
}
|
||||
return "`" + name + "`"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) QuoteIdentifier(name string) string {
|
||||
// 去除已有的引号(反引号和双引号)
|
||||
name = strings.Trim(name, "`\"")
|
||||
return "`" + name + "`"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) QuoteChar() string {
|
||||
return "`"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Placeholder(index int) string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Placeholders(count int, startIndex int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
placeholders := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
placeholders[i] = "?"
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) SupportsLastInsertId() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) ReturningClause(column string) string {
|
||||
return "" // MySQL 不支持 RETURNING
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2)
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = d.Quote(col)
|
||||
}
|
||||
|
||||
placeholders := d.Placeholders(len(columns), 1)
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
// 检查是否是 [#] 标记的直接 SQL
|
||||
if strings.HasSuffix(col, "[#]") {
|
||||
// 这种情况在调用处处理
|
||||
updateParts[i] = col
|
||||
} else {
|
||||
quotedCol := d.Quote(col)
|
||||
updateParts[i] = quotedCol + " = VALUES(" + quotedCol + ")"
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s",
|
||||
d.Quote(table),
|
||||
strings.Join(quotedCols, ", "),
|
||||
placeholders,
|
||||
strings.Join(updateParts, ", "))
|
||||
}
|
||||
|
||||
// PostgreSQLDialect PostgreSQL 方言实现
|
||||
type PostgreSQLDialect struct{}
|
||||
|
||||
func (d *PostgreSQLDialect) GetName() string {
|
||||
return "postgres"
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) Quote(name string) string {
|
||||
// 如果已经包含点号(表.字段)或空格(别名),不添加引号
|
||||
if strings.Contains(name, ".") || strings.Contains(name, " ") {
|
||||
return name
|
||||
}
|
||||
return "\"" + name + "\""
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) QuoteIdentifier(name string) string {
|
||||
// 去除已有的引号(反引号和双引号)
|
||||
name = strings.Trim(name, "`\"")
|
||||
return "\"" + name + "\""
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) QuoteChar() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) Placeholder(index int) string {
|
||||
return fmt.Sprintf("$%d", index)
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) Placeholders(count int, startIndex int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
placeholders := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
placeholders[i] = fmt.Sprintf("$%d", startIndex+i)
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) SupportsLastInsertId() bool {
|
||||
return false // PostgreSQL 需要使用 RETURNING
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) ReturningClause(column string) string {
|
||||
return " RETURNING " + d.Quote(column)
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES ($1, $2)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = EXCLUDED.col1, col2 = EXCLUDED.col2
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = d.Quote(col)
|
||||
}
|
||||
|
||||
placeholders := d.Placeholders(len(columns), 1)
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = d.Quote(key)
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if strings.HasSuffix(col, "[#]") {
|
||||
updateParts[i] = col
|
||||
} else {
|
||||
quotedCol := d.Quote(col)
|
||||
updateParts[i] = quotedCol + " = EXCLUDED." + quotedCol
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s",
|
||||
d.Quote(table),
|
||||
strings.Join(quotedCols, ", "),
|
||||
placeholders,
|
||||
strings.Join(quotedUniqueKeys, ", "),
|
||||
strings.Join(updateParts, ", "))
|
||||
}
|
||||
|
||||
// SQLiteDialect SQLite 方言实现
|
||||
type SQLiteDialect struct{}
|
||||
|
||||
func (d *SQLiteDialect) GetName() string {
|
||||
return "sqlite3"
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) Quote(name string) string {
|
||||
// 如果已经包含点号(表.字段)或空格(别名),不添加引号
|
||||
if strings.Contains(name, ".") || strings.Contains(name, " ") {
|
||||
return name
|
||||
}
|
||||
return "\"" + name + "\""
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) QuoteIdentifier(name string) string {
|
||||
// 去除已有的引号(反引号和双引号)
|
||||
name = strings.Trim(name, "`\"")
|
||||
return "\"" + name + "\""
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) QuoteChar() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) Placeholder(index int) string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) Placeholders(count int, startIndex int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
placeholders := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
placeholders[i] = "?"
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) SupportsLastInsertId() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) ReturningClause(column string) string {
|
||||
return "" // SQLite 3.35+ 支持 RETURNING,但为兼容性暂不使用
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = excluded.col1, col2 = excluded.col2
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = d.Quote(col)
|
||||
}
|
||||
|
||||
placeholders := d.Placeholders(len(columns), 1)
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = d.Quote(key)
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if strings.HasSuffix(col, "[#]") {
|
||||
updateParts[i] = col
|
||||
} else {
|
||||
quotedCol := d.Quote(col)
|
||||
updateParts[i] = quotedCol + " = excluded." + quotedCol
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s",
|
||||
d.Quote(table),
|
||||
strings.Join(quotedCols, ", "),
|
||||
placeholders,
|
||||
strings.Join(quotedUniqueKeys, ", "),
|
||||
strings.Join(updateParts, ", "))
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDialectQuoteIdentifier 测试方言的 QuoteIdentifier 方法
|
||||
func TestDialectQuoteIdentifier(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dialect Dialect
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
// MySQL 方言测试
|
||||
{"MySQL simple", &MySQLDialect{}, "name", "`name`"},
|
||||
{"MySQL with backticks", &MySQLDialect{}, "`name`", "`name`"},
|
||||
{"MySQL with quotes", &MySQLDialect{}, "\"name\"", "`name`"},
|
||||
|
||||
// PostgreSQL 方言测试
|
||||
{"PostgreSQL simple", &PostgreSQLDialect{}, "name", "\"name\""},
|
||||
{"PostgreSQL with backticks", &PostgreSQLDialect{}, "`name`", "\"name\""},
|
||||
{"PostgreSQL with quotes", &PostgreSQLDialect{}, "\"name\"", "\"name\""},
|
||||
|
||||
// SQLite 方言测试
|
||||
{"SQLite simple", &SQLiteDialect{}, "name", "\"name\""},
|
||||
{"SQLite with backticks", &SQLiteDialect{}, "`name`", "\"name\""},
|
||||
{"SQLite with quotes", &SQLiteDialect{}, "\"name\"", "\"name\""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.dialect.QuoteIdentifier(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("QuoteIdentifier(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDialectQuoteChar 测试方言的 QuoteChar 方法
|
||||
func TestDialectQuoteChar(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dialect Dialect
|
||||
expected string
|
||||
}{
|
||||
{"MySQL", &MySQLDialect{}, "`"},
|
||||
{"PostgreSQL", &PostgreSQLDialect{}, "\""},
|
||||
{"SQLite", &SQLiteDialect{}, "\""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.dialect.QuoteChar()
|
||||
if result != tt.expected {
|
||||
t.Errorf("QuoteChar() = %q, want %q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIdentifierProcessorTableName 测试表名处理
|
||||
func TestIdentifierProcessorTableName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dialect Dialect
|
||||
prefix string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
// MySQL 无前缀
|
||||
{"MySQL no prefix", &MySQLDialect{}, "", "order", "`order`"},
|
||||
{"MySQL no prefix with backticks", &MySQLDialect{}, "", "`order`", "`order`"},
|
||||
|
||||
// MySQL 有前缀
|
||||
{"MySQL with prefix", &MySQLDialect{}, "app_", "order", "`app_order`"},
|
||||
{"MySQL with prefix and backticks", &MySQLDialect{}, "app_", "`order`", "`app_order`"},
|
||||
|
||||
// PostgreSQL 无前缀
|
||||
{"PostgreSQL no prefix", &PostgreSQLDialect{}, "", "order", "\"order\""},
|
||||
|
||||
// PostgreSQL 有前缀
|
||||
{"PostgreSQL with prefix", &PostgreSQLDialect{}, "app_", "order", "\"app_order\""},
|
||||
{"PostgreSQL with prefix and quotes", &PostgreSQLDialect{}, "app_", "\"order\"", "\"app_order\""},
|
||||
|
||||
// SQLite 有前缀
|
||||
{"SQLite with prefix", &SQLiteDialect{}, "app_", "user", "\"app_user\""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
processor := NewIdentifierProcessor(tt.dialect, tt.prefix)
|
||||
result := processor.ProcessTableName(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ProcessTableName(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIdentifierProcessorColumn 测试列名处理(包括 table.column 格式)
|
||||
func TestIdentifierProcessorColumn(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dialect Dialect
|
||||
prefix string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
// 单独列名
|
||||
{"MySQL simple column", &MySQLDialect{}, "", "name", "`name`"},
|
||||
{"MySQL simple column with prefix", &MySQLDialect{}, "app_", "name", "`name`"},
|
||||
|
||||
// table.column 格式
|
||||
{"MySQL table.column no prefix", &MySQLDialect{}, "", "order.name", "`order`.`name`"},
|
||||
{"MySQL table.column with prefix", &MySQLDialect{}, "app_", "order.name", "`app_order`.`name`"},
|
||||
{"MySQL table.column with backticks", &MySQLDialect{}, "app_", "`order`.name", "`app_order`.`name`"},
|
||||
|
||||
// PostgreSQL
|
||||
{"PostgreSQL table.column with prefix", &PostgreSQLDialect{}, "app_", "order.name", "\"app_order\".\"name\""},
|
||||
{"PostgreSQL table.column with quotes", &PostgreSQLDialect{}, "app_", "\"order\".name", "\"app_order\".\"name\""},
|
||||
|
||||
// SQLite
|
||||
{"SQLite table.column with prefix", &SQLiteDialect{}, "app_", "user.email", "\"app_user\".\"email\""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
processor := NewIdentifierProcessor(tt.dialect, tt.prefix)
|
||||
result := processor.ProcessColumn(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ProcessColumn(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIdentifierProcessorConditionString 测试条件字符串处理
|
||||
func TestIdentifierProcessorConditionString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dialect Dialect
|
||||
prefix string
|
||||
input string
|
||||
contains []string // 结果应该包含这些字符串
|
||||
}{
|
||||
// MySQL 简单条件
|
||||
{
|
||||
"MySQL simple condition",
|
||||
&MySQLDialect{},
|
||||
"app_",
|
||||
"user.id = order.user_id",
|
||||
[]string{"`app_user`", "`app_order`"},
|
||||
},
|
||||
// MySQL 复杂条件
|
||||
{
|
||||
"MySQL complex condition",
|
||||
&MySQLDialect{},
|
||||
"app_",
|
||||
"user.id = order.user_id AND order.status = 1",
|
||||
[]string{"`app_user`", "`app_order`"},
|
||||
},
|
||||
// PostgreSQL
|
||||
{
|
||||
"PostgreSQL condition",
|
||||
&PostgreSQLDialect{},
|
||||
"app_",
|
||||
"user.id = order.user_id",
|
||||
[]string{"\"app_user\"", "\"app_order\""},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
processor := NewIdentifierProcessor(tt.dialect, tt.prefix)
|
||||
result := processor.ProcessConditionString(tt.input)
|
||||
for _, expected := range tt.contains {
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("ProcessConditionString(%q) = %q, should contain %q", tt.input, result, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHoTimeDBHelperMethods 测试 HoTimeDB 的辅助方法 T() 和 C()
|
||||
func TestHoTimeDBHelperMethods(t *testing.T) {
|
||||
// 创建 MySQL 数据库实例
|
||||
mysqlDB := &HoTimeDB{
|
||||
Type: "mysql",
|
||||
Prefix: "app_",
|
||||
}
|
||||
mysqlDB.initDialect()
|
||||
|
||||
// 测试 T() 方法
|
||||
t.Run("MySQL T() method", func(t *testing.T) {
|
||||
result := mysqlDB.T("order")
|
||||
expected := "`app_order`"
|
||||
if result != expected {
|
||||
t.Errorf("T(\"order\") = %q, want %q", result, expected)
|
||||
}
|
||||
})
|
||||
|
||||
// 测试 C() 方法(两个参数)
|
||||
t.Run("MySQL C() method with two args", func(t *testing.T) {
|
||||
result := mysqlDB.C("order", "name")
|
||||
expected := "`app_order`.`name`"
|
||||
if result != expected {
|
||||
t.Errorf("C(\"order\", \"name\") = %q, want %q", result, expected)
|
||||
}
|
||||
})
|
||||
|
||||
// 测试 C() 方法(一个参数,点号格式)
|
||||
t.Run("MySQL C() method with dot notation", func(t *testing.T) {
|
||||
result := mysqlDB.C("order.name")
|
||||
expected := "`app_order`.`name`"
|
||||
if result != expected {
|
||||
t.Errorf("C(\"order.name\") = %q, want %q", result, expected)
|
||||
}
|
||||
})
|
||||
|
||||
// 创建 PostgreSQL 数据库实例
|
||||
pgDB := &HoTimeDB{
|
||||
Type: "postgres",
|
||||
Prefix: "app_",
|
||||
}
|
||||
pgDB.initDialect()
|
||||
|
||||
// 测试 PostgreSQL 的 T() 方法
|
||||
t.Run("PostgreSQL T() method", func(t *testing.T) {
|
||||
result := pgDB.T("order")
|
||||
expected := "\"app_order\""
|
||||
if result != expected {
|
||||
t.Errorf("T(\"order\") = %q, want %q", result, expected)
|
||||
}
|
||||
})
|
||||
|
||||
// 测试 PostgreSQL 的 C() 方法
|
||||
t.Run("PostgreSQL C() method", func(t *testing.T) {
|
||||
result := pgDB.C("order", "name")
|
||||
expected := "\"app_order\".\"name\""
|
||||
if result != expected {
|
||||
t.Errorf("C(\"order\", \"name\") = %q, want %q", result, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 打印测试结果(用于调试)
|
||||
func ExampleIdentifierProcessor() {
|
||||
// MySQL 示例
|
||||
mysqlProcessor := NewIdentifierProcessor(&MySQLDialect{}, "app_")
|
||||
fmt.Println("MySQL:")
|
||||
fmt.Println(" Table:", mysqlProcessor.ProcessTableName("order"))
|
||||
fmt.Println(" Column:", mysqlProcessor.ProcessColumn("order.name"))
|
||||
fmt.Println(" Condition:", mysqlProcessor.ProcessConditionString("user.id = order.user_id"))
|
||||
|
||||
// PostgreSQL 示例
|
||||
pgProcessor := NewIdentifierProcessor(&PostgreSQLDialect{}, "app_")
|
||||
fmt.Println("PostgreSQL:")
|
||||
fmt.Println(" Table:", pgProcessor.ProcessTableName("order"))
|
||||
fmt.Println(" Column:", pgProcessor.ProcessColumn("order.name"))
|
||||
fmt.Println(" Condition:", pgProcessor.ProcessConditionString("user.id = order.user_id"))
|
||||
|
||||
// Output:
|
||||
// MySQL:
|
||||
// Table: `app_order`
|
||||
// Column: `app_order`.`name`
|
||||
// Condition: `app_user`.`id` = `app_order`.`user_id`
|
||||
// PostgreSQL:
|
||||
// Table: "app_order"
|
||||
// Column: "app_order"."name"
|
||||
// Condition: "app_user"."id" = "app_order"."user_id"
|
||||
}
|
||||
-1082
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IdentifierProcessor 标识符处理器
|
||||
// 用于处理表名、字段名的前缀添加和引号转换
|
||||
type IdentifierProcessor struct {
|
||||
dialect Dialect
|
||||
prefix string
|
||||
}
|
||||
|
||||
// NewIdentifierProcessor 创建标识符处理器
|
||||
func NewIdentifierProcessor(dialect Dialect, prefix string) *IdentifierProcessor {
|
||||
return &IdentifierProcessor{
|
||||
dialect: dialect,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
// 系统数据库列表,这些数据库不添加前缀
|
||||
var systemDatabases = map[string]bool{
|
||||
"INFORMATION_SCHEMA": true,
|
||||
"information_schema": true,
|
||||
"mysql": true,
|
||||
"performance_schema": true,
|
||||
"sys": true,
|
||||
"pg_catalog": true,
|
||||
"pg_toast": true,
|
||||
}
|
||||
|
||||
// ProcessTableName 处理表名(添加前缀+引号)
|
||||
// 输入: "order" 或 "`order`" 或 "\"order\"" 或 "INFORMATION_SCHEMA.TABLES"
|
||||
// 输出: "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL/SQLite)
|
||||
// 对于 database.table 格式,会分别处理,系统数据库不添加前缀
|
||||
func (p *IdentifierProcessor) ProcessTableName(name string) string {
|
||||
// 去除已有的引号
|
||||
name = p.stripQuotes(name)
|
||||
|
||||
// 检查是否包含空格(别名情况,如 "order AS o")
|
||||
if strings.Contains(name, " ") {
|
||||
// 处理别名情况
|
||||
parts := strings.SplitN(name, " ", 2)
|
||||
tableName := p.stripQuotes(parts[0])
|
||||
alias := parts[1]
|
||||
// 递归处理表名部分(可能包含点号)
|
||||
return p.ProcessTableName(tableName) + " " + alias
|
||||
}
|
||||
|
||||
// 检查是否包含点号(database.table 格式)
|
||||
if strings.Contains(name, ".") {
|
||||
parts := p.splitTableColumn(name)
|
||||
if len(parts) == 2 {
|
||||
dbName := p.stripQuotes(parts[0])
|
||||
tableName := p.stripQuotes(parts[1])
|
||||
// 系统数据库不添加前缀
|
||||
if systemDatabases[dbName] {
|
||||
return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(tableName)
|
||||
}
|
||||
// 非系统数据库,只给表名添加前缀
|
||||
return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(p.prefix+tableName)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加前缀和引号
|
||||
return p.dialect.QuoteIdentifier(p.prefix + name)
|
||||
}
|
||||
|
||||
// ProcessTableNameNoPrefix 处理表名(只添加引号,不添加前缀)
|
||||
// 用于已经包含前缀的情况
|
||||
func (p *IdentifierProcessor) ProcessTableNameNoPrefix(name string) string {
|
||||
name = p.stripQuotes(name)
|
||||
if strings.Contains(name, " ") {
|
||||
parts := strings.SplitN(name, " ", 2)
|
||||
tableName := p.stripQuotes(parts[0])
|
||||
alias := parts[1]
|
||||
return p.dialect.QuoteIdentifier(tableName) + " " + alias
|
||||
}
|
||||
return p.dialect.QuoteIdentifier(name)
|
||||
}
|
||||
|
||||
// ProcessColumn 处理 table.column 格式
|
||||
// 输入: "name" 或 "order.name" 或 "`order`.name" 或 "`order`.`name`"
|
||||
// 输出: "`name`" 或 "`app_order`.`name`" (MySQL)
|
||||
func (p *IdentifierProcessor) ProcessColumn(name string) string {
|
||||
// 检查是否包含点号
|
||||
if !strings.Contains(name, ".") {
|
||||
// 单独的列名,只加引号
|
||||
return p.dialect.QuoteIdentifier(p.stripQuotes(name))
|
||||
}
|
||||
|
||||
// 处理 table.column 格式
|
||||
parts := p.splitTableColumn(name)
|
||||
if len(parts) == 2 {
|
||||
tableName := p.stripQuotes(parts[0])
|
||||
columnName := p.stripQuotes(parts[1])
|
||||
// 表名添加前缀
|
||||
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(columnName)
|
||||
}
|
||||
|
||||
// 无法解析,返回原样但转换引号
|
||||
return p.convertQuotes(name)
|
||||
}
|
||||
|
||||
// ProcessColumnNoPrefix 处理 table.column 格式(不添加前缀)
|
||||
func (p *IdentifierProcessor) ProcessColumnNoPrefix(name string) string {
|
||||
if !strings.Contains(name, ".") {
|
||||
return p.dialect.QuoteIdentifier(p.stripQuotes(name))
|
||||
}
|
||||
|
||||
parts := p.splitTableColumn(name)
|
||||
if len(parts) == 2 {
|
||||
tableName := p.stripQuotes(parts[0])
|
||||
columnName := p.stripQuotes(parts[1])
|
||||
return p.dialect.QuoteIdentifier(tableName) + "." + p.dialect.QuoteIdentifier(columnName)
|
||||
}
|
||||
|
||||
return p.convertQuotes(name)
|
||||
}
|
||||
|
||||
// ProcessConditionString 智能解析条件字符串(如 ON 条件)
|
||||
// 输入: "user.id = order.user_id AND order.status = 1"
|
||||
// 输出: "`app_user`.`id` = `app_order`.`user_id` AND `app_order`.`status` = 1" (MySQL)
|
||||
func (p *IdentifierProcessor) ProcessConditionString(condition string) string {
|
||||
if condition == "" {
|
||||
return condition
|
||||
}
|
||||
|
||||
result := condition
|
||||
|
||||
// 首先处理已有完整引号的情况 `table`.`column` 或 "table"."column"
|
||||
// 这些需要先处理,因为它们的格式最明确
|
||||
fullyQuotedPattern := regexp.MustCompile("[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]\\.[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]")
|
||||
result = fullyQuotedPattern.ReplaceAllStringFunc(result, func(match string) string {
|
||||
parts := fullyQuotedPattern.FindStringSubmatch(match)
|
||||
if len(parts) == 3 {
|
||||
tableName := parts[1]
|
||||
colName := parts[2]
|
||||
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName)
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
||||
// 然后处理部分引号的情况 `table`.column 或 "table".column
|
||||
// 注意:需要避免匹配已处理的内容(已经是双引号包裹的)
|
||||
quotedTablePattern := regexp.MustCompile("[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]\\.([a-zA-Z_][a-zA-Z0-9_]*)(?:[^`\"]|$)")
|
||||
result = quotedTablePattern.ReplaceAllStringFunc(result, func(match string) string {
|
||||
parts := quotedTablePattern.FindStringSubmatch(match)
|
||||
if len(parts) >= 3 {
|
||||
tableName := parts[1]
|
||||
colName := parts[2]
|
||||
// 保留末尾字符(如果有)
|
||||
suffix := ""
|
||||
if len(match) > len(parts[0])-1 {
|
||||
lastChar := match[len(match)-1]
|
||||
if lastChar != '`' && lastChar != '"' && !isIdentChar(lastChar) {
|
||||
suffix = string(lastChar)
|
||||
}
|
||||
}
|
||||
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName) + suffix
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
||||
// 最后处理无引号的情况 table.column
|
||||
// 使用更精确的正则,确保不匹配已处理的内容
|
||||
unquotedPattern := regexp.MustCompile(`([^` + "`" + `"\w]|^)([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)([^` + "`" + `"\w(]|$)`)
|
||||
result = unquotedPattern.ReplaceAllStringFunc(result, func(match string) string {
|
||||
parts := unquotedPattern.FindStringSubmatch(match)
|
||||
if len(parts) >= 5 {
|
||||
prefix := parts[1] // 前面的边界字符
|
||||
tableName := parts[2]
|
||||
colName := parts[3]
|
||||
suffix := parts[4] // 后面的边界字符
|
||||
return prefix + p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName) + suffix
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isIdentChar 判断是否是标识符字符
|
||||
func isIdentChar(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'
|
||||
}
|
||||
|
||||
// ProcessFieldList 处理字段列表字符串
|
||||
// 输入: "order.id, user.name AS uname, COUNT(*)"
|
||||
// 输出: "`app_order`.`id`, `app_user`.`name` AS uname, COUNT(*)" (MySQL)
|
||||
func (p *IdentifierProcessor) ProcessFieldList(fields string) string {
|
||||
if fields == "" || fields == "*" {
|
||||
return fields
|
||||
}
|
||||
|
||||
// 使用与 ProcessConditionString 相同的逻辑
|
||||
return p.ProcessConditionString(fields)
|
||||
}
|
||||
|
||||
// stripQuotes 去除标识符两端的引号(反引号或双引号)
|
||||
func (p *IdentifierProcessor) stripQuotes(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
// 去除反引号
|
||||
if strings.HasPrefix(name, "`") && strings.HasSuffix(name, "`") {
|
||||
return name[1 : len(name)-1]
|
||||
}
|
||||
// 去除双引号
|
||||
if strings.HasPrefix(name, "\"") && strings.HasSuffix(name, "\"") {
|
||||
return name[1 : len(name)-1]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// splitTableColumn 分割 table.column 格式
|
||||
// 支持: table.column, `table`.column, `table`.`column`, "table".column 等
|
||||
func (p *IdentifierProcessor) splitTableColumn(name string) []string {
|
||||
// 先尝试按点号分割
|
||||
dotIndex := -1
|
||||
|
||||
// 查找不在引号内的点号
|
||||
inQuote := false
|
||||
quoteChar := byte(0)
|
||||
for i := 0; i < len(name); i++ {
|
||||
c := name[i]
|
||||
if c == '`' || c == '"' {
|
||||
if !inQuote {
|
||||
inQuote = true
|
||||
quoteChar = c
|
||||
} else if c == quoteChar {
|
||||
inQuote = false
|
||||
}
|
||||
} else if c == '.' && !inQuote {
|
||||
dotIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dotIndex == -1 {
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
return []string{name[:dotIndex], name[dotIndex+1:]}
|
||||
}
|
||||
|
||||
// convertQuotes 将已有的引号转换为当前方言的引号格式
|
||||
func (p *IdentifierProcessor) convertQuotes(name string) string {
|
||||
quoteChar := p.dialect.QuoteChar()
|
||||
// 替换反引号
|
||||
name = strings.ReplaceAll(name, "`", quoteChar)
|
||||
// 如果目标是反引号,需要替换双引号
|
||||
if quoteChar == "`" {
|
||||
name = strings.ReplaceAll(name, "\"", quoteChar)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// GetDialect 获取方言
|
||||
func (p *IdentifierProcessor) GetDialect() Dialect {
|
||||
return p.dialect
|
||||
}
|
||||
|
||||
// GetPrefix 获取前缀
|
||||
func (p *IdentifierProcessor) GetPrefix() string {
|
||||
return p.prefix
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// md5 生成查询的 MD5 哈希(用于缓存)
|
||||
func (that *HoTimeDB) md5(query string, args ...interface{}) string {
|
||||
strByte, _ := json.Marshal(args)
|
||||
str := Md5(query + ":" + string(strByte))
|
||||
return str
|
||||
}
|
||||
|
||||
// Query 执行查询 SQL
|
||||
func (that *HoTimeDB) Query(query string, args ...interface{}) []Map {
|
||||
return that.queryWithRetry(query, false, args...)
|
||||
}
|
||||
|
||||
// queryWithRetry 内部查询方法,支持重试标记
|
||||
func (that *HoTimeDB) queryWithRetry(query string, retried bool, args ...interface{}) []Map {
|
||||
// 预处理数组占位符 ?[]
|
||||
query, args = that.expandArrayPlaceholder(query, args)
|
||||
|
||||
// 保存调试信息(加锁保护)
|
||||
that.mu.Lock()
|
||||
that.LastQuery = query
|
||||
that.LastData = args
|
||||
that.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
if that.Mode != 0 {
|
||||
that.mu.RLock()
|
||||
that.Log.Info("SQL:"+that.LastQuery, " DATA:", that.LastData, " ERROR:", that.LastErr.GetError())
|
||||
that.mu.RUnlock()
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
var resl *sql.Rows
|
||||
|
||||
// 主从数据库切换,只有select语句有从数据库
|
||||
db := that.DB
|
||||
if that.SlaveDB != nil {
|
||||
db = that.SlaveDB
|
||||
}
|
||||
|
||||
if db == nil {
|
||||
err = errors.New("没有初始化数据库")
|
||||
that.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理参数中的 slice 类型
|
||||
processedArgs := that.processArgs(args)
|
||||
|
||||
if that.Tx != nil {
|
||||
resl, err = that.Tx.Query(query, processedArgs...)
|
||||
} else {
|
||||
resl, err = db.Query(query, processedArgs...)
|
||||
}
|
||||
|
||||
that.LastErr.SetError(err)
|
||||
if err != nil {
|
||||
// 如果还没重试过,尝试 Ping 后重试一次
|
||||
if !retried {
|
||||
if pingErr := db.Ping(); pingErr == nil {
|
||||
return that.queryWithRetry(query, true, args...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return that.Row(resl)
|
||||
}
|
||||
|
||||
// Exec 执行非查询 SQL
|
||||
func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Error) {
|
||||
return that.execWithRetry(query, false, args...)
|
||||
}
|
||||
|
||||
// execWithRetry 内部执行方法,支持重试标记
|
||||
func (that *HoTimeDB) execWithRetry(query string, retried bool, args ...interface{}) (sql.Result, *Error) {
|
||||
// 预处理数组占位符 ?[]
|
||||
query, args = that.expandArrayPlaceholder(query, args)
|
||||
|
||||
// 保存调试信息(加锁保护)
|
||||
that.mu.Lock()
|
||||
that.LastQuery = query
|
||||
that.LastData = args
|
||||
that.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
if that.Mode != 0 {
|
||||
that.mu.RLock()
|
||||
that.Log.Info("SQL: "+that.LastQuery, " DATA: ", that.LastData, " ERROR: ", that.LastErr.GetError())
|
||||
that.mu.RUnlock()
|
||||
}
|
||||
}()
|
||||
|
||||
var e error
|
||||
var resl sql.Result
|
||||
|
||||
if that.DB == nil {
|
||||
err := errors.New("没有初始化数据库")
|
||||
that.LastErr.SetError(err)
|
||||
return nil, that.LastErr
|
||||
}
|
||||
|
||||
// 处理参数中的 slice 类型
|
||||
processedArgs := that.processArgs(args)
|
||||
|
||||
if that.Tx != nil {
|
||||
resl, e = that.Tx.Exec(query, processedArgs...)
|
||||
} else {
|
||||
resl, e = that.DB.Exec(query, processedArgs...)
|
||||
}
|
||||
|
||||
that.LastErr.SetError(e)
|
||||
// 判断是否连接断开了,如果还没重试过,尝试重试一次
|
||||
if e != nil {
|
||||
if !retried {
|
||||
if pingErr := that.DB.Ping(); pingErr == nil {
|
||||
return that.execWithRetry(query, true, args...)
|
||||
}
|
||||
}
|
||||
return resl, that.LastErr
|
||||
}
|
||||
|
||||
return resl, that.LastErr
|
||||
}
|
||||
|
||||
// processArgs 处理参数中的 slice 类型
|
||||
func (that *HoTimeDB) processArgs(args []interface{}) []interface{} {
|
||||
processedArgs := make([]interface{}, len(args))
|
||||
copy(processedArgs, args)
|
||||
for key := range processedArgs {
|
||||
arg := processedArgs[key]
|
||||
if arg == nil {
|
||||
continue
|
||||
}
|
||||
argType := reflect.ValueOf(arg).Type().String()
|
||||
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
|
||||
argLis := ObjToSlice(arg)
|
||||
// 将slice转为逗号分割字符串
|
||||
argStr := ""
|
||||
for i := 0; i < len(argLis); i++ {
|
||||
if i == len(argLis)-1 {
|
||||
argStr += ObjToStr(argLis[i])
|
||||
} else {
|
||||
argStr += ObjToStr(argLis[i]) + ","
|
||||
}
|
||||
}
|
||||
processedArgs[key] = argStr
|
||||
}
|
||||
}
|
||||
return processedArgs
|
||||
}
|
||||
|
||||
// expandArrayPlaceholder 展开 IN (?) / NOT IN (?) 中的数组参数
|
||||
// 自动识别 IN/NOT IN (?) 模式,当参数是数组时展开为多个 ?
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// db.Query("SELECT * FROM user WHERE id IN (?)", []int{1, 2, 3})
|
||||
// // 展开为: SELECT * FROM user WHERE id IN (?, ?, ?) 参数: [1, 2, 3]
|
||||
//
|
||||
// db.Query("SELECT * FROM user WHERE id IN (?)", []int{})
|
||||
// // 展开为: SELECT * FROM user WHERE 1=0 参数: [] (空集合的IN永假)
|
||||
//
|
||||
// db.Query("SELECT * FROM user WHERE id NOT IN (?)", []int{})
|
||||
// // 展开为: SELECT * FROM user WHERE 1=1 参数: [] (空集合的NOT IN永真)
|
||||
//
|
||||
// db.Query("SELECT * FROM user WHERE id = ?", 1)
|
||||
// // 保持不变: SELECT * FROM user WHERE id = ? 参数: [1]
|
||||
func (that *HoTimeDB) expandArrayPlaceholder(query string, args []interface{}) (string, []interface{}) {
|
||||
if len(args) == 0 || !strings.Contains(query, "?") {
|
||||
return query, args
|
||||
}
|
||||
|
||||
// 检查是否有数组参数
|
||||
hasArray := false
|
||||
for _, arg := range args {
|
||||
if arg == nil {
|
||||
continue
|
||||
}
|
||||
argType := reflect.ValueOf(arg).Type().String()
|
||||
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
|
||||
hasArray = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasArray {
|
||||
return query, args
|
||||
}
|
||||
|
||||
newArgs := make([]interface{}, 0, len(args))
|
||||
result := strings.Builder{}
|
||||
argIndex := 0
|
||||
|
||||
for i := 0; i < len(query); i++ {
|
||||
if query[i] == '?' && argIndex < len(args) {
|
||||
arg := args[argIndex]
|
||||
argIndex++
|
||||
|
||||
if arg == nil {
|
||||
result.WriteByte('?')
|
||||
newArgs = append(newArgs, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
argType := reflect.ValueOf(arg).Type().String()
|
||||
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
|
||||
// 是数组参数,检查是否在 IN (...) 或 NOT IN (...) 中
|
||||
prevPart := result.String()
|
||||
prevUpper := strings.ToUpper(prevPart)
|
||||
|
||||
// 查找最近的 NOT IN ( 模式
|
||||
notInIndex := strings.LastIndex(prevUpper, " NOT IN (")
|
||||
notInIndex2 := strings.LastIndex(prevUpper, " NOT IN(")
|
||||
if notInIndex2 > notInIndex {
|
||||
notInIndex = notInIndex2
|
||||
}
|
||||
|
||||
// 查找最近的 IN ( 模式(但要排除 NOT IN 的情况)
|
||||
inIndex := strings.LastIndex(prevUpper, " IN (")
|
||||
inIndex2 := strings.LastIndex(prevUpper, " IN(")
|
||||
if inIndex2 > inIndex {
|
||||
inIndex = inIndex2
|
||||
}
|
||||
|
||||
// 判断是 NOT IN 还是 IN
|
||||
// 注意:" NOT IN (" 包含 " IN (",所以如果找到的 IN 位置在 NOT IN 范围内,应该优先判断为 NOT IN
|
||||
isNotIn := false
|
||||
matchIndex := -1
|
||||
if notInIndex != -1 {
|
||||
// 检查 inIndex 是否在 notInIndex 范围内(即 NOT IN 的 IN 部分)
|
||||
// NOT IN ( 的 IN ( 部分从 notInIndex + 4 开始
|
||||
if inIndex != -1 && inIndex >= notInIndex && inIndex <= notInIndex+5 {
|
||||
// inIndex 是 NOT IN 的一部分,使用 NOT IN
|
||||
isNotIn = true
|
||||
matchIndex = notInIndex
|
||||
} else if inIndex == -1 || notInIndex > inIndex {
|
||||
// 没有独立的 IN,或 NOT IN 在 IN 之后
|
||||
isNotIn = true
|
||||
matchIndex = notInIndex
|
||||
} else {
|
||||
// 有独立的 IN 且在 NOT IN 之后
|
||||
matchIndex = inIndex
|
||||
}
|
||||
} else if inIndex != -1 {
|
||||
matchIndex = inIndex
|
||||
}
|
||||
|
||||
// 检查 IN ( 后面是否只有空格(即当前 ? 紧跟在 IN ( 后面)
|
||||
isInPattern := false
|
||||
if matchIndex != -1 {
|
||||
afterIn := prevPart[matchIndex:]
|
||||
// 找到 ( 的位置
|
||||
parenIdx := strings.Index(afterIn, "(")
|
||||
if parenIdx != -1 {
|
||||
afterParen := strings.TrimSpace(afterIn[parenIdx+1:])
|
||||
if afterParen == "" {
|
||||
isInPattern = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isInPattern {
|
||||
// 在 IN (...) 或 NOT IN (...) 模式中
|
||||
argList := ObjToSlice(arg)
|
||||
if len(argList) == 0 {
|
||||
// 空数组处理:需要找到字段名的开始位置
|
||||
// 往前找最近的 AND/OR/WHERE/(,以确定条件的开始位置
|
||||
truncateIndex := matchIndex
|
||||
searchPart := prevUpper[:matchIndex]
|
||||
|
||||
// 找最近的分隔符位置
|
||||
andIdx := strings.LastIndex(searchPart, " AND ")
|
||||
orIdx := strings.LastIndex(searchPart, " OR ")
|
||||
whereIdx := strings.LastIndex(searchPart, " WHERE ")
|
||||
parenIdx := strings.LastIndex(searchPart, "(")
|
||||
|
||||
// 取最靠后的分隔符
|
||||
sepIndex := -1
|
||||
sepLen := 0
|
||||
if andIdx > sepIndex {
|
||||
sepIndex = andIdx
|
||||
sepLen = 5 // " AND "
|
||||
}
|
||||
if orIdx > sepIndex {
|
||||
sepIndex = orIdx
|
||||
sepLen = 4 // " OR "
|
||||
}
|
||||
if whereIdx > sepIndex {
|
||||
sepIndex = whereIdx
|
||||
sepLen = 7 // " WHERE "
|
||||
}
|
||||
if parenIdx > sepIndex {
|
||||
sepIndex = parenIdx
|
||||
sepLen = 1 // "("
|
||||
}
|
||||
|
||||
if sepIndex != -1 {
|
||||
truncateIndex = sepIndex + sepLen
|
||||
}
|
||||
|
||||
result.Reset()
|
||||
result.WriteString(prevPart[:truncateIndex])
|
||||
if isNotIn {
|
||||
// NOT IN 空集合 = 永真
|
||||
result.WriteString(" 1=1 ")
|
||||
} else {
|
||||
// IN 空集合 = 永假
|
||||
result.WriteString(" 1=0 ")
|
||||
}
|
||||
// 跳过后面的 )
|
||||
for j := i + 1; j < len(query); j++ {
|
||||
if query[j] == ')' {
|
||||
i = j
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if len(argList) == 1 {
|
||||
// 单元素数组
|
||||
result.WriteByte('?')
|
||||
newArgs = append(newArgs, argList[0])
|
||||
} else {
|
||||
// 多元素数组,展开为多个 ?
|
||||
for j := 0; j < len(argList); j++ {
|
||||
if j > 0 {
|
||||
result.WriteString(", ")
|
||||
}
|
||||
result.WriteByte('?')
|
||||
newArgs = append(newArgs, argList[j])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 不在 IN 模式中,保持原有行为(数组会被 processArgs 转为逗号字符串)
|
||||
result.WriteByte('?')
|
||||
newArgs = append(newArgs, arg)
|
||||
}
|
||||
} else {
|
||||
// 非数组参数
|
||||
result.WriteByte('?')
|
||||
newArgs = append(newArgs, arg)
|
||||
}
|
||||
} else {
|
||||
result.WriteByte(query[i])
|
||||
}
|
||||
}
|
||||
|
||||
return result.String(), newArgs
|
||||
}
|
||||
|
||||
// Row 数据库数据解析
|
||||
func (that *HoTimeDB) Row(resl *sql.Rows) []Map {
|
||||
dest := make([]Map, 0)
|
||||
strs, _ := resl.Columns()
|
||||
|
||||
for i := 0; resl.Next(); i++ {
|
||||
lis := make(Map, 0)
|
||||
a := make([]interface{}, len(strs))
|
||||
|
||||
b := make([]interface{}, len(a))
|
||||
for j := 0; j < len(a); j++ {
|
||||
b[j] = &a[j]
|
||||
}
|
||||
err := resl.Scan(b...)
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
for j := 0; j < len(a); j++ {
|
||||
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
|
||||
lis[strs[j]] = string(a[j].([]byte))
|
||||
} else {
|
||||
lis[strs[j]] = a[j] // 取实际类型
|
||||
}
|
||||
}
|
||||
|
||||
dest = append(dest, lis)
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Action 事务操作
|
||||
// 如果 action 返回 true 则提交事务;返回 false 则回滚
|
||||
func (that *HoTimeDB) Action(action func(db HoTimeDB) (isSuccess bool)) (isSuccess bool) {
|
||||
db := HoTimeDB{
|
||||
DB: that.DB,
|
||||
ContextBase: that.ContextBase,
|
||||
DBName: that.DBName,
|
||||
HoTimeCache: that.HoTimeCache,
|
||||
Log: that.Log,
|
||||
Type: that.Type,
|
||||
Prefix: that.Prefix,
|
||||
LastQuery: that.LastQuery,
|
||||
LastData: that.LastData,
|
||||
ConnectFunc: that.ConnectFunc,
|
||||
LastErr: that.LastErr,
|
||||
limit: that.limit,
|
||||
Tx: that.Tx,
|
||||
SlaveDB: that.SlaveDB,
|
||||
Mode: that.Mode,
|
||||
Dialect: that.Dialect,
|
||||
mu: sync.RWMutex{},
|
||||
limitMu: sync.Mutex{},
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
db.Tx = tx
|
||||
|
||||
isSuccess = action(db)
|
||||
|
||||
if !isSuccess {
|
||||
err = db.Tx.Rollback()
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return isSuccess
|
||||
}
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
err = db.Tx.Commit()
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 条件关键字
|
||||
var condition = []string{"AND", "OR"}
|
||||
|
||||
// 特殊关键字(支持大小写)
|
||||
var vcond = []string{"GROUP", "ORDER", "LIMIT", "DISTINCT", "HAVING", "OFFSET"}
|
||||
|
||||
// normalizeKey 标准化关键字(转大写)
|
||||
func normalizeKey(k string) string {
|
||||
upper := strings.ToUpper(k)
|
||||
for _, v := range vcond {
|
||||
if upper == v {
|
||||
return v
|
||||
}
|
||||
}
|
||||
for _, v := range condition {
|
||||
if upper == v {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// isConditionKey 判断是否是条件关键字
|
||||
func isConditionKey(k string) bool {
|
||||
upper := strings.ToUpper(k)
|
||||
for _, v := range condition {
|
||||
if upper == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isVcondKey 判断是否是特殊关键字
|
||||
func isVcondKey(k string) bool {
|
||||
upper := strings.ToUpper(k)
|
||||
for _, v := range vcond {
|
||||
if upper == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// where 语句解析
|
||||
func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
||||
where := ""
|
||||
res := make([]interface{}, 0)
|
||||
|
||||
// 标准化 Map 的 key(大小写兼容)
|
||||
normalizedData := Map{}
|
||||
for k, v := range data {
|
||||
normalizedData[normalizeKey(k)] = v
|
||||
}
|
||||
data = normalizedData
|
||||
|
||||
// 收集所有 key 并排序
|
||||
testQu := []string{}
|
||||
for key := range data {
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
sort.Strings(testQu)
|
||||
|
||||
// 追踪普通条件数量,用于自动添加 AND
|
||||
normalCondCount := 0
|
||||
|
||||
for _, k := range testQu {
|
||||
v := data[k]
|
||||
|
||||
// 检查是否是 AND/OR 条件关键字
|
||||
if isConditionKey(k) {
|
||||
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
||||
where += tw
|
||||
res = append(res, ts...)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否是特殊关键字(GROUP, ORDER, LIMIT 等)
|
||||
if isVcondKey(k) {
|
||||
continue // 特殊关键字在后面单独处理
|
||||
}
|
||||
|
||||
// 处理普通条件字段
|
||||
// 空切片的 IN 条件应该生成永假条件(1=0),而不是跳过
|
||||
if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 {
|
||||
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
||||
if !strings.HasSuffix(k, "[!]") {
|
||||
// IN 空数组 -> 生成永假条件
|
||||
if normalCondCount > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += "1=0 "
|
||||
normalCondCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 {
|
||||
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
||||
if !strings.HasSuffix(k, "[!]") {
|
||||
// IN 空数组 -> 生成永假条件
|
||||
if normalCondCount > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += "1=0 "
|
||||
normalCondCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
tv, vv := that.varCond(k, v)
|
||||
if tv != "" {
|
||||
// 自动添加 AND 连接符
|
||||
if normalCondCount > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += tv
|
||||
normalCondCount++
|
||||
res = append(res, vv...)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 WHERE 关键字
|
||||
// 先去除首尾空格,检查是否有实际条件内容
|
||||
trimmedWhere := strings.TrimSpace(where)
|
||||
if len(trimmedWhere) != 0 {
|
||||
hasWhere := true
|
||||
for _, v := range vcond {
|
||||
if strings.Index(trimmedWhere, v) == 0 {
|
||||
hasWhere = false
|
||||
}
|
||||
}
|
||||
|
||||
if hasWhere {
|
||||
where = " WHERE " + trimmedWhere + " "
|
||||
}
|
||||
} else {
|
||||
// 没有实际条件内容,重置 where
|
||||
where = ""
|
||||
}
|
||||
|
||||
// 处理特殊字符(按固定顺序:GROUP, HAVING, ORDER, LIMIT, OFFSET)
|
||||
specialOrder := []string{"GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "DISTINCT"}
|
||||
for _, vcondKey := range specialOrder {
|
||||
v, exists := data[vcondKey]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
switch vcondKey {
|
||||
case "GROUP":
|
||||
where += " GROUP BY "
|
||||
where += that.formatVcondValue(v)
|
||||
case "HAVING":
|
||||
// HAVING 条件处理
|
||||
if havingMap, ok := v.(Map); ok {
|
||||
havingWhere, havingRes := that.cond("AND", havingMap)
|
||||
if havingWhere != "" {
|
||||
where += " HAVING " + strings.TrimSpace(havingWhere) + " "
|
||||
res = append(res, havingRes...)
|
||||
}
|
||||
}
|
||||
case "ORDER":
|
||||
where += " ORDER BY "
|
||||
where += that.formatVcondValue(v)
|
||||
case "LIMIT":
|
||||
where += " LIMIT "
|
||||
where += that.formatVcondValue(v)
|
||||
case "OFFSET":
|
||||
where += " OFFSET " + ObjToStr(v) + " "
|
||||
case "DISTINCT":
|
||||
// DISTINCT 通常在 SELECT 中处理,这里暂时忽略
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// formatVcondValue 格式化特殊关键字的值
|
||||
func (that *HoTimeDB) formatVcondValue(v interface{}) string {
|
||||
result := ""
|
||||
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
result += " " + vs.GetString(i) + " "
|
||||
if len(vs) != i+1 {
|
||||
result += ", "
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += " " + ObjToStr(v) + " "
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// varCond 变量条件解析
|
||||
func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
|
||||
where := ""
|
||||
res := make([]interface{}, 0)
|
||||
length := len(k)
|
||||
processor := that.GetProcessor()
|
||||
|
||||
if k == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
where += " " + ObjToStr(v) + " "
|
||||
} else if k == "[##]" {
|
||||
// 直接添加 SQL 片段(key 为 [##] 时)
|
||||
where += " " + ObjToStr(v) + " "
|
||||
} else if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' {
|
||||
def := false
|
||||
|
||||
switch Substr(k, length-3, 3) {
|
||||
case "[>]":
|
||||
k = strings.Replace(k, "[>]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + ">? "
|
||||
res = append(res, v)
|
||||
case "[<]":
|
||||
k = strings.Replace(k, "[<]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + "<? "
|
||||
res = append(res, v)
|
||||
case "[!]":
|
||||
k = strings.Replace(k, "[!]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where, res = that.notIn(k, v, where, res)
|
||||
case "[#]":
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += " " + k + "=" + ObjToStr(v) + " "
|
||||
case "[##]": // 直接添加value到sql,需要考虑防注入
|
||||
where += " " + ObjToStr(v)
|
||||
case "[#!]":
|
||||
k = strings.Replace(k, "[#!]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += " " + k + "!=" + ObjToStr(v) + " "
|
||||
case "[!#]":
|
||||
k = strings.Replace(k, "[!#]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += " " + k + "!=" + ObjToStr(v) + " "
|
||||
case "[~]":
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + " LIKE ? "
|
||||
v = "%" + ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[!~]": // 左边任意
|
||||
k = strings.Replace(k, "[!~]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + " LIKE ? "
|
||||
v = "%" + ObjToStr(v) + ""
|
||||
res = append(res, v)
|
||||
case "[~!]": // 右边任意
|
||||
k = strings.Replace(k, "[~!]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + " LIKE ? "
|
||||
v = ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[~~]": // 手动任意
|
||||
k = strings.Replace(k, "[~~]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + " LIKE ? "
|
||||
res = append(res, v)
|
||||
default:
|
||||
def = true
|
||||
}
|
||||
|
||||
if def {
|
||||
switch Substr(k, length-4, 4) {
|
||||
case "[>=]":
|
||||
k = strings.Replace(k, "[>=]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + ">=? "
|
||||
res = append(res, v)
|
||||
case "[<=]":
|
||||
k = strings.Replace(k, "[<=]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + "<=? "
|
||||
res = append(res, v)
|
||||
case "[><]":
|
||||
k = strings.Replace(k, "[><]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + " NOT BETWEEN ? AND ? "
|
||||
vs := ObjToSlice(v)
|
||||
res = append(res, vs[0])
|
||||
res = append(res, vs[1])
|
||||
case "[<>]":
|
||||
k = strings.Replace(k, "[<>]", "", -1)
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
where += k + " BETWEEN ? AND ? "
|
||||
vs := ObjToSlice(v)
|
||||
res = append(res, vs[0])
|
||||
res = append(res, vs[1])
|
||||
default:
|
||||
where, res = that.handleDefaultCondition(k, v, where, res)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
where, res = that.handlePlainField(k, v, where, res)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// handleDefaultCondition 处理默认条件(带方括号但不是特殊操作符)
|
||||
func (that *HoTimeDB) handleDefaultCondition(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
processor := that.GetProcessor()
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
|
||||
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
if len(vs) == 0 {
|
||||
// IN 空数组 -> 生成永假条件
|
||||
where += "1=0 "
|
||||
return where, res
|
||||
}
|
||||
|
||||
if len(vs) == 1 {
|
||||
where += k + "=? "
|
||||
res = append(res, vs[0])
|
||||
return where, res
|
||||
}
|
||||
|
||||
// IN 优化:连续整数转为 BETWEEN
|
||||
where, res = that.optimizeInCondition(k, vs, where, res)
|
||||
} else {
|
||||
where += k + "=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// handlePlainField 处理普通字段(无方括号)
|
||||
func (that *HoTimeDB) handlePlainField(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
processor := that.GetProcessor()
|
||||
k = processor.ProcessColumn(k) + " "
|
||||
|
||||
if v == nil {
|
||||
where += k + " IS NULL "
|
||||
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
if len(vs) == 0 {
|
||||
// IN 空数组 -> 生成永假条件
|
||||
where += "1=0 "
|
||||
return where, res
|
||||
}
|
||||
|
||||
if len(vs) == 1 {
|
||||
where += k + "=? "
|
||||
res = append(res, vs[0])
|
||||
return where, res
|
||||
}
|
||||
|
||||
// IN 优化
|
||||
where, res = that.optimizeInCondition(k, vs, where, res)
|
||||
} else {
|
||||
where += k + "=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// optimizeInCondition 优化 IN 条件(连续整数转为 BETWEEN)
|
||||
func (that *HoTimeDB) optimizeInCondition(k string, vs Slice, where string, res []interface{}) (string, []interface{}) {
|
||||
min := int64(0)
|
||||
isMin := true
|
||||
IsRange := true
|
||||
num := int64(0)
|
||||
isNum := true
|
||||
|
||||
where1 := ""
|
||||
res1 := Slice{}
|
||||
where2 := k + " IN ("
|
||||
res2 := Slice{}
|
||||
|
||||
for kvs := 0; kvs <= len(vs); kvs++ {
|
||||
vsv := int64(0)
|
||||
if kvs < len(vs) {
|
||||
vsv = vs.GetCeilInt64(kvs)
|
||||
// 确保是全部是int类型
|
||||
if ObjToStr(vsv) != vs.GetString(kvs) {
|
||||
IsRange = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isNum {
|
||||
isNum = false
|
||||
num = vsv
|
||||
} else {
|
||||
num++
|
||||
}
|
||||
if isMin {
|
||||
isMin = false
|
||||
min = vsv
|
||||
}
|
||||
// 不等于则到了分路口
|
||||
if num != vsv {
|
||||
// between
|
||||
if num-min > 1 {
|
||||
if where1 != "" {
|
||||
where1 += " OR " + k + " BETWEEN ? AND ? "
|
||||
} else {
|
||||
where1 += k + " BETWEEN ? AND ? "
|
||||
}
|
||||
res1 = append(res1, min)
|
||||
res1 = append(res1, num-1)
|
||||
} else {
|
||||
where2 += "?,"
|
||||
res2 = append(res2, min)
|
||||
}
|
||||
min = vsv
|
||||
num = vsv
|
||||
}
|
||||
}
|
||||
|
||||
if IsRange {
|
||||
where3 := ""
|
||||
|
||||
if where1 != "" {
|
||||
where3 += where1
|
||||
res = append(res, res1...)
|
||||
}
|
||||
|
||||
if len(res2) == 1 {
|
||||
if where3 == "" {
|
||||
where3 += k + " = ? "
|
||||
} else {
|
||||
where3 += " OR " + k + " = ? "
|
||||
}
|
||||
res = append(res, res2...)
|
||||
} else if len(res2) > 1 {
|
||||
where2 = where2[:len(where2)-1]
|
||||
if where3 == "" {
|
||||
where3 += where2 + ")"
|
||||
} else {
|
||||
where3 += " OR " + where2 + ")"
|
||||
}
|
||||
res = append(res, res2...)
|
||||
}
|
||||
|
||||
if where3 != "" {
|
||||
where += "(" + where3 + ")"
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// 非连续整数,使用普通 IN
|
||||
where += k + " IN ("
|
||||
res = append(res, vs...)
|
||||
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if i+1 != len(vs) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// notIn NOT IN 条件处理
|
||||
func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
if v == nil {
|
||||
where += k + " IS NOT NULL "
|
||||
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
if len(vs) == 0 {
|
||||
return where, res
|
||||
}
|
||||
where += k + " NOT IN ("
|
||||
res = append(res, vs...)
|
||||
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if i+1 != len(vs) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
}
|
||||
} else {
|
||||
where += k + " !=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// cond 条件组合处理
|
||||
func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
|
||||
where := " "
|
||||
res := make([]interface{}, 0)
|
||||
lens := len(data)
|
||||
|
||||
testQu := []string{}
|
||||
for key := range data {
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
sort.Strings(testQu)
|
||||
|
||||
for _, k := range testQu {
|
||||
v := data[k]
|
||||
x := 0
|
||||
for i := 0; i < len(condition); i++ {
|
||||
if condition[i] == strings.ToUpper(k) {
|
||||
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
||||
if lens--; lens <= 0 {
|
||||
where += "(" + tw + ") "
|
||||
} else {
|
||||
where += "(" + tw + ") " + tag + " "
|
||||
}
|
||||
|
||||
res = append(res, ts...)
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
if x == len(condition) {
|
||||
tv, vv := that.varCond(k, v)
|
||||
if tv == "" {
|
||||
lens--
|
||||
continue
|
||||
}
|
||||
res = append(res, vv...)
|
||||
if lens--; lens <= 0 {
|
||||
where += tv + ""
|
||||
} else {
|
||||
where += tv + " " + tag + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
# HoTimeDB API 快速参考
|
||||
|
||||
## 条件查询语法规则
|
||||
|
||||
**新版本改进:**
|
||||
- 多条件自动用 AND 连接,无需手动包装
|
||||
- 关键字支持大小写(如 `LIMIT` 和 `limit` 都有效)
|
||||
- 新增 `HAVING` 和独立 `OFFSET` 支持
|
||||
|
||||
```go
|
||||
// ✅ 推荐:简化语法(多条件自动 AND)
|
||||
Map{"status": 1, "age[>]": 18}
|
||||
// 生成: WHERE `status`=? AND `age`>?
|
||||
|
||||
// ✅ 仍然支持:显式 AND 包装(向后兼容)
|
||||
Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
},
|
||||
}
|
||||
|
||||
// ✅ 混合条件和特殊关键字
|
||||
Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"ORDER": "id DESC", // 或 "order": "id DESC"
|
||||
"LIMIT": 10, // 或 "limit": 10
|
||||
}
|
||||
```
|
||||
|
||||
## 基本方法
|
||||
|
||||
### 数据库连接
|
||||
```go
|
||||
database.SetConnect(func() (master, slave *sql.DB) { ... })
|
||||
database.InitDb()
|
||||
```
|
||||
|
||||
### 链式查询构建器
|
||||
```go
|
||||
// 创建查询构建器
|
||||
builder := database.Table("tablename")
|
||||
|
||||
// 设置条件
|
||||
builder.Where(key, value)
|
||||
builder.And(key, value) 或 builder.And(map)
|
||||
builder.Or(key, value) 或 builder.Or(map)
|
||||
|
||||
// JOIN操作
|
||||
builder.LeftJoin(table, condition)
|
||||
builder.RightJoin(table, condition)
|
||||
builder.InnerJoin(table, condition)
|
||||
builder.FullJoin(table, condition)
|
||||
builder.Join(map) // 通用JOIN
|
||||
|
||||
// 排序和分组
|
||||
builder.Order(fields...)
|
||||
builder.Group(fields...)
|
||||
builder.Limit(args...)
|
||||
builder.Having(map) // 新增
|
||||
|
||||
// 分页
|
||||
builder.Page(page, pageSize)
|
||||
builder.Offset(offset) // 新增
|
||||
|
||||
// 执行查询
|
||||
builder.Select(fields...) // 返回 []Map
|
||||
builder.Get(fields...) // 返回 Map
|
||||
builder.Count() // 返回 int
|
||||
builder.Update(data) // 返回 int64
|
||||
builder.Delete() // 返回 int64
|
||||
```
|
||||
|
||||
## CRUD 操作
|
||||
|
||||
### 查询 (Select)
|
||||
```go
|
||||
// 基本查询
|
||||
data := database.Select("table")
|
||||
data := database.Select("table", "field1,field2")
|
||||
data := database.Select("table", []string{"field1", "field2"})
|
||||
data := database.Select("table", "*", whereMap)
|
||||
|
||||
// 带JOIN查询
|
||||
data := database.Select("table", joinSlice, "fields", whereMap)
|
||||
```
|
||||
|
||||
### 获取单条 (Get)
|
||||
```go
|
||||
// 自动添加 LIMIT 1
|
||||
row := database.Get("table", "fields", whereMap)
|
||||
```
|
||||
|
||||
### 插入 (Insert)
|
||||
```go
|
||||
id := database.Insert("table", dataMap)
|
||||
// 返回新插入记录的ID
|
||||
```
|
||||
|
||||
### 批量插入 (Inserts) - 新增
|
||||
```go
|
||||
// 使用 []Map 格式,更直观简洁
|
||||
affected := database.Inserts("table", []Map{
|
||||
{"col1": "val1", "col2": "val2", "col3": "val3"},
|
||||
{"col1": "val4", "col2": "val5", "col3": "val6"},
|
||||
})
|
||||
// 返回受影响的行数
|
||||
|
||||
// 支持 [#] 标记直接 SQL
|
||||
affected := database.Inserts("log", []Map{
|
||||
{"user_id": 1, "created_time[#]": "NOW()"},
|
||||
{"user_id": 2, "created_time[#]": "NOW()"},
|
||||
})
|
||||
```
|
||||
|
||||
### 更新 (Update)
|
||||
```go
|
||||
affected := database.Update("table", dataMap, whereMap)
|
||||
// 返回受影响的行数
|
||||
```
|
||||
|
||||
### Upsert - 新增
|
||||
```go
|
||||
// 使用 Slice 格式
|
||||
affected := database.Upsert("table",
|
||||
dataMap, // 插入数据
|
||||
Slice{"unique_key"}, // 唯一键
|
||||
Slice{"col1", "col2"}, // 冲突时更新的字段
|
||||
)
|
||||
|
||||
// 也支持可变参数
|
||||
affected := database.Upsert("table", dataMap, Slice{"id"}, "col1", "col2")
|
||||
// 返回受影响的行数
|
||||
```
|
||||
|
||||
### 删除 (Delete)
|
||||
```go
|
||||
affected := database.Delete("table", whereMap)
|
||||
// 返回删除的行数
|
||||
```
|
||||
|
||||
## 聚合函数
|
||||
|
||||
### 计数
|
||||
```go
|
||||
count := database.Count("table")
|
||||
count := database.Count("table", whereMap)
|
||||
count := database.Count("table", joinSlice, whereMap)
|
||||
```
|
||||
|
||||
### 求和
|
||||
```go
|
||||
sum := database.Sum("table", "column")
|
||||
sum := database.Sum("table", "column", whereMap)
|
||||
```
|
||||
|
||||
### 平均值 - 新增
|
||||
```go
|
||||
avg := database.Avg("table", "column")
|
||||
avg := database.Avg("table", "column", whereMap)
|
||||
```
|
||||
|
||||
### 最大值 - 新增
|
||||
```go
|
||||
max := database.Max("table", "column")
|
||||
max := database.Max("table", "column", whereMap)
|
||||
```
|
||||
|
||||
### 最小值 - 新增
|
||||
```go
|
||||
min := database.Min("table", "column")
|
||||
min := database.Min("table", "column", whereMap)
|
||||
```
|
||||
|
||||
## 分页查询
|
||||
```go
|
||||
// 设置分页
|
||||
database.Page(page, pageSize)
|
||||
|
||||
// 分页查询
|
||||
data := database.Page(page, pageSize).PageSelect("table", "fields", whereMap)
|
||||
```
|
||||
|
||||
## 条件语法参考
|
||||
|
||||
### 比较操作符
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": value` | `field = ?` | 等于 |
|
||||
| `"field[!]": value` | `field != ?` | 不等于 |
|
||||
| `"field[>]": value` | `field > ?` | 大于 |
|
||||
| `"field[>=]": value` | `field >= ?` | 大于等于 |
|
||||
| `"field[<]": value` | `field < ?` | 小于 |
|
||||
| `"field[<=]": value` | `field <= ?` | 小于等于 |
|
||||
|
||||
### 模糊查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field[~]": "keyword"` | `field LIKE '%keyword%'` | 包含 |
|
||||
| `"field[~!]": "keyword"` | `field LIKE 'keyword%'` | 以...开头 |
|
||||
| `"field[!~]": "keyword"` | `field LIKE '%keyword'` | 以...结尾 |
|
||||
| `"field[~~]": "%keyword%"` | `field LIKE '%keyword%'` | 手动LIKE |
|
||||
|
||||
### 范围查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field[<>]": [min, max]` | `field BETWEEN ? AND ?` | 区间内 |
|
||||
| `"field[><]": [min, max]` | `field NOT BETWEEN ? AND ?` | 区间外 |
|
||||
|
||||
### 集合查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": [v1, v2, v3]` | `field IN (?, ?, ?)` | 在集合中 |
|
||||
| `"field[!]": [v1, v2, v3]` | `field NOT IN (?, ?, ?)` | 不在集合中 |
|
||||
|
||||
### NULL查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": nil` | `field IS NULL` | 为空 |
|
||||
| `"field[!]": nil` | `field IS NOT NULL` | 不为空 |
|
||||
|
||||
### 直接SQL
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field[#]": "NOW()"` | `field = NOW()` | 直接SQL函数 |
|
||||
| `"[##]": "a > b"` | `a > b` | 直接SQL片段 |
|
||||
| `"field[#!]": "1"` | `field != 1` | 不等于(不参数化) |
|
||||
|
||||
## 逻辑连接符
|
||||
|
||||
### AND 条件
|
||||
```go
|
||||
// 简化语法(推荐)
|
||||
whereMap := Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
}
|
||||
// 生成: WHERE `status`=? AND `age`>?
|
||||
|
||||
// 显式 AND(向后兼容)
|
||||
whereMap := Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### OR 条件
|
||||
```go
|
||||
whereMap := Map{
|
||||
"OR": Map{
|
||||
"status": 1,
|
||||
"type": 2,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 嵌套条件
|
||||
```go
|
||||
whereMap := Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"OR": Map{
|
||||
"age[<]": 30,
|
||||
"level[>]": 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## JOIN 语法
|
||||
|
||||
### 传统语法
|
||||
```go
|
||||
joinSlice := Slice{
|
||||
Map{"[>]profile": "user.id = profile.user_id"}, // LEFT JOIN
|
||||
Map{"[<]department": "user.dept_id = department.id"}, // RIGHT JOIN
|
||||
Map{"[><]role": "user.role_id = role.id"}, // INNER JOIN
|
||||
Map{"[<>]group": "user.group_id = group.id"}, // FULL JOIN
|
||||
}
|
||||
```
|
||||
|
||||
### 链式语法
|
||||
```go
|
||||
builder.LeftJoin("profile", "user.id = profile.user_id")
|
||||
builder.RightJoin("department", "user.dept_id = department.id")
|
||||
builder.InnerJoin("role", "user.role_id = role.id")
|
||||
builder.FullJoin("group", "user.group_id = group.id")
|
||||
```
|
||||
|
||||
## 特殊字段语法
|
||||
|
||||
### ORDER BY
|
||||
```go
|
||||
Map{
|
||||
"ORDER": []string{"created_time DESC", "id ASC"},
|
||||
}
|
||||
// 或
|
||||
Map{
|
||||
"order": "created_time DESC", // 支持小写
|
||||
}
|
||||
```
|
||||
|
||||
### GROUP BY
|
||||
```go
|
||||
Map{
|
||||
"GROUP": []string{"department", "level"},
|
||||
}
|
||||
// 或
|
||||
Map{
|
||||
"group": "department", // 支持小写
|
||||
}
|
||||
```
|
||||
|
||||
### HAVING - 新增
|
||||
```go
|
||||
Map{
|
||||
"GROUP": "dept_id",
|
||||
"HAVING": Map{
|
||||
"COUNT(*).[>]": 5,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### LIMIT
|
||||
```go
|
||||
Map{
|
||||
"LIMIT": []int{10, 20}, // offset 10, limit 20
|
||||
}
|
||||
// 或
|
||||
Map{
|
||||
"limit": 20, // limit 20,支持小写
|
||||
}
|
||||
```
|
||||
|
||||
### OFFSET - 新增
|
||||
```go
|
||||
Map{
|
||||
"LIMIT": 10,
|
||||
"OFFSET": 20, // 独立的 OFFSET
|
||||
}
|
||||
```
|
||||
|
||||
## 事务处理
|
||||
```go
|
||||
success := database.Action(func(tx HoTimeDB) bool {
|
||||
// 在这里执行数据库操作
|
||||
// 返回 true 提交事务
|
||||
// 返回 false 回滚事务
|
||||
|
||||
id := tx.Insert("table", data)
|
||||
if id == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
affected := tx.Update("table2", data2, where2)
|
||||
if affected == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
return true // 提交
|
||||
})
|
||||
```
|
||||
|
||||
## 原生SQL执行
|
||||
|
||||
### 查询
|
||||
```go
|
||||
results := database.Query("SELECT * FROM user WHERE age > ?", 18)
|
||||
```
|
||||
|
||||
### 执行
|
||||
```go
|
||||
result, err := database.Exec("UPDATE user SET status = ? WHERE id = ?", 1, 100)
|
||||
affected, _ := result.RowsAffected()
|
||||
```
|
||||
|
||||
## PostgreSQL 支持 - 新增
|
||||
|
||||
```go
|
||||
// 配置 PostgreSQL
|
||||
database := &db.HoTimeDB{
|
||||
Type: "postgres", // 设置类型
|
||||
}
|
||||
|
||||
// 框架自动处理差异:
|
||||
// - 占位符: ? -> $1, $2, $3...
|
||||
// - 引号: `name` -> "name"
|
||||
// - Upsert: ON DUPLICATE KEY -> ON CONFLICT
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
```go
|
||||
// 检查最后的错误
|
||||
if database.LastErr.GetError() != nil {
|
||||
fmt.Println("错误:", database.LastErr.GetError())
|
||||
}
|
||||
|
||||
// 查看最后执行的SQL
|
||||
fmt.Println("SQL:", database.LastQuery)
|
||||
fmt.Println("参数:", database.LastData)
|
||||
```
|
||||
|
||||
## 工具方法
|
||||
|
||||
### 数据库信息
|
||||
```go
|
||||
prefix := database.GetPrefix() // 获取表前缀
|
||||
dbType := database.GetType() // 获取数据库类型
|
||||
dialect := database.GetDialect() // 获取方言适配器
|
||||
```
|
||||
|
||||
### 设置模式
|
||||
```go
|
||||
database.Mode = 0 // 生产模式
|
||||
database.Mode = 1 // 测试模式
|
||||
database.Mode = 2 // 开发模式(输出SQL日志)
|
||||
```
|
||||
|
||||
## 常用查询模式
|
||||
|
||||
### 分页列表查询
|
||||
```go
|
||||
// 获取总数
|
||||
total := database.Count("user", Map{"status": 1})
|
||||
|
||||
// 分页数据
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
Order("created_time DESC").
|
||||
Page(page, pageSize).
|
||||
Select("id,name,email,created_time")
|
||||
|
||||
// 计算分页信息
|
||||
totalPages := (total + pageSize - 1) / pageSize
|
||||
```
|
||||
|
||||
### 关联查询
|
||||
```go
|
||||
orders := database.Table("order").
|
||||
LeftJoin("user", "order.user_id = user.id").
|
||||
LeftJoin("product", "order.product_id = product.id").
|
||||
Where("order.status", "paid").
|
||||
Select(`
|
||||
order.*,
|
||||
user.name as user_name,
|
||||
product.title as product_title
|
||||
`)
|
||||
```
|
||||
|
||||
### 统计查询
|
||||
```go
|
||||
stats := database.Select("order",
|
||||
"user_id, COUNT(*) as order_count, SUM(amount) as total_amount",
|
||||
Map{
|
||||
"status": "paid",
|
||||
"created_time[>]": "2023-01-01",
|
||||
"GROUP": "user_id",
|
||||
"ORDER": "total_amount DESC",
|
||||
})
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
```go
|
||||
// 批量插入(使用 []Map 格式)
|
||||
affected := database.Inserts("user", []Map{
|
||||
{"name": "用户1", "email": "user1@example.com", "status": 1},
|
||||
{"name": "用户2", "email": "user2@example.com", "status": 1},
|
||||
{"name": "用户3", "email": "user3@example.com", "status": 1},
|
||||
})
|
||||
|
||||
// Upsert(插入或更新,使用 Slice 格式)
|
||||
affected := database.Upsert("user",
|
||||
Map{"id": 1, "name": "新名称", "email": "new@example.com"},
|
||||
Slice{"id"},
|
||||
Slice{"name", "email"},
|
||||
)
|
||||
```
|
||||
|
||||
## 链式调用完整示例
|
||||
|
||||
```go
|
||||
// 复杂查询链式调用
|
||||
result := database.Table("order").
|
||||
LeftJoin("user", "order.user_id = user.id").
|
||||
LeftJoin("product", "order.product_id = product.id").
|
||||
Where("order.status", "paid").
|
||||
And("order.created_time[>]", "2023-01-01").
|
||||
And(Map{
|
||||
"OR": Map{
|
||||
"user.level": "vip",
|
||||
"order.amount[>]": 1000,
|
||||
},
|
||||
}).
|
||||
Group("user.id").
|
||||
Having(Map{"total_amount[>]": 500}).
|
||||
Order("total_amount DESC").
|
||||
Page(1, 20).
|
||||
Select(`
|
||||
user.id,
|
||||
user.name,
|
||||
user.email,
|
||||
COUNT(order.id) as order_count,
|
||||
SUM(order.amount) as total_amount
|
||||
`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*快速参考版本: 2.0*
|
||||
*更新日期: 2026年1月*
|
||||
|
||||
**详细说明:**
|
||||
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) - 完整教程
|
||||
@@ -0,0 +1,833 @@
|
||||
# HoTimeDB ORM 使用说明书
|
||||
|
||||
## 概述
|
||||
|
||||
HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite、PostgreSQL等数据库,并集成了缓存、事务、链式查询等功能。
|
||||
|
||||
## 目录
|
||||
|
||||
- [快速开始](#快速开始)
|
||||
- [数据库配置](#数据库配置)
|
||||
- [基本操作](#基本操作)
|
||||
- [查询(Select)](#查询select)
|
||||
- [获取单条记录(Get)](#获取单条记录get)
|
||||
- [插入(Insert)](#插入insert)
|
||||
- [批量插入(Inserts)](#批量插入Inserts)
|
||||
- [更新(Update)](#更新update)
|
||||
- [Upsert操作](#upsert操作)
|
||||
- [删除(Delete)](#删除delete)
|
||||
- [链式查询构建器](#链式查询构建器)
|
||||
- [条件查询语法](#条件查询语法)
|
||||
- [JOIN操作](#join操作)
|
||||
- [分页查询](#分页查询)
|
||||
- [聚合函数](#聚合函数)
|
||||
- [事务处理](#事务处理)
|
||||
- [缓存机制](#缓存机制)
|
||||
- [PostgreSQL支持](#postgresql支持)
|
||||
- [高级特性](#高级特性)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 初始化数据库连接
|
||||
|
||||
```go
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/db"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// 创建连接函数
|
||||
func createConnection() (master, slave *sql.DB) {
|
||||
master, _ = sql.Open("mysql", "user:password@tcp(localhost:3306)/database")
|
||||
// slave是可选的,用于读写分离
|
||||
slave = master // 或者连接到从数据库
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化HoTimeDB
|
||||
database := &db.HoTimeDB{
|
||||
Type: "mysql", // 可选:mysql, sqlite3, postgres
|
||||
}
|
||||
database.SetConnect(createConnection)
|
||||
```
|
||||
|
||||
## 数据库配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
```go
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
ContextBase
|
||||
DBName string
|
||||
*cache.HoTimeCache
|
||||
Log *logrus.Logger
|
||||
Type string // 数据库类型:mysql, sqlite3, postgres
|
||||
Prefix string // 表前缀
|
||||
LastQuery string // 最后执行的SQL
|
||||
LastData []interface{} // 最后的参数
|
||||
ConnectFunc func(err ...*Error) (*sql.DB, *sql.DB)
|
||||
LastErr *Error
|
||||
limit Slice
|
||||
*sql.Tx // 事务对象
|
||||
SlaveDB *sql.DB // 从数据库
|
||||
Mode int // 0生产模式,1测试模式,2开发模式
|
||||
Dialect Dialect // 数据库方言适配器
|
||||
}
|
||||
```
|
||||
|
||||
### 设置表前缀
|
||||
|
||||
```go
|
||||
database.Prefix = "app_"
|
||||
```
|
||||
|
||||
### 设置运行模式
|
||||
|
||||
```go
|
||||
database.Mode = 2 // 开发模式,会输出SQL日志
|
||||
```
|
||||
|
||||
## 基本操作
|
||||
|
||||
### 查询(Select)
|
||||
|
||||
#### 基本查询
|
||||
|
||||
```go
|
||||
// 查询所有字段
|
||||
users := database.Select("user")
|
||||
|
||||
// 查询指定字段
|
||||
users := database.Select("user", "id,name,email")
|
||||
|
||||
// 查询指定字段(数组形式)
|
||||
users := database.Select("user", []string{"id", "name", "email"})
|
||||
|
||||
// 单条件查询
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 多条件查询(自动用 AND 连接)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
})
|
||||
// 生成: WHERE `status`=? AND `age`>?
|
||||
```
|
||||
|
||||
#### 复杂条件查询
|
||||
|
||||
```go
|
||||
// 简化语法:多条件自动用 AND 连接
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"name[~]": "张",
|
||||
})
|
||||
// 生成: WHERE `status`=? AND `age`>? AND `name` LIKE ?
|
||||
|
||||
// 显式 AND 条件(与上面等效)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"AND": common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"name[~]": "张",
|
||||
},
|
||||
})
|
||||
|
||||
// OR 条件
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"OR": common.Map{
|
||||
"status": 1,
|
||||
"type": 2,
|
||||
},
|
||||
})
|
||||
|
||||
// 混合条件(嵌套 AND/OR)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"AND": common.Map{
|
||||
"status": 1,
|
||||
"OR": common.Map{
|
||||
"age[<]": 30,
|
||||
"level[>]": 5,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 带 ORDER BY、LIMIT 等特殊条件(关键字支持大小写)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"ORDER": "id DESC", // 或 "order": "id DESC"
|
||||
"LIMIT": 10, // 或 "limit": 10
|
||||
})
|
||||
|
||||
// 带多个特殊条件
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"OR": common.Map{
|
||||
"level": "vip",
|
||||
"balance[>]": 1000,
|
||||
},
|
||||
"ORDER": []string{"created_time DESC", "id ASC"},
|
||||
"GROUP": "department",
|
||||
"LIMIT": []int{0, 20}, // offset 0, limit 20
|
||||
})
|
||||
|
||||
// 使用 HAVING 过滤分组结果
|
||||
users := database.Select("user", "dept_id, COUNT(*) as cnt", common.Map{
|
||||
"GROUP": "dept_id",
|
||||
"HAVING": common.Map{
|
||||
"cnt[>]": 5,
|
||||
},
|
||||
})
|
||||
|
||||
// 使用独立 OFFSET
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"LIMIT": 10,
|
||||
"OFFSET": 20,
|
||||
})
|
||||
```
|
||||
|
||||
### 获取单条记录(Get)
|
||||
|
||||
```go
|
||||
// 获取单个用户
|
||||
user := database.Get("user", "*", common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
// 获取指定字段
|
||||
user := database.Get("user", "id,name,email", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
```
|
||||
|
||||
### 插入(Insert)
|
||||
|
||||
```go
|
||||
// 基本插入
|
||||
id := database.Insert("user", common.Map{
|
||||
"name": "张三",
|
||||
"email": "zhangsan@example.com",
|
||||
"age": 25,
|
||||
"status": 1,
|
||||
"created_time[#]": "NOW()", // [#]表示直接插入SQL函数
|
||||
})
|
||||
|
||||
// 返回插入的ID
|
||||
fmt.Println("插入的用户ID:", id)
|
||||
```
|
||||
|
||||
### 批量插入(Inserts)
|
||||
|
||||
```go
|
||||
// 批量插入多条记录(使用 []Map 格式,更直观)
|
||||
affected := database.Inserts("user", []common.Map{
|
||||
{"name": "张三", "email": "zhang@example.com", "age": 25},
|
||||
{"name": "李四", "email": "li@example.com", "age": 30},
|
||||
{"name": "王五", "email": "wang@example.com", "age": 28},
|
||||
})
|
||||
// 生成: INSERT INTO `user` (`age`, `email`, `name`) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)
|
||||
|
||||
fmt.Printf("批量插入 %d 条记录\n", affected)
|
||||
|
||||
// 支持 [#] 标记直接插入 SQL 表达式
|
||||
affected := database.Inserts("log", []common.Map{
|
||||
{"user_id": 1, "action": "login", "created_time[#]": "NOW()"},
|
||||
{"user_id": 2, "action": "logout", "created_time[#]": "NOW()"},
|
||||
})
|
||||
```
|
||||
|
||||
### 更新(Update)
|
||||
|
||||
```go
|
||||
// 基本更新
|
||||
affected := database.Update("user", common.Map{
|
||||
"name": "李四",
|
||||
"email": "lisi@example.com",
|
||||
"updated_time[#]": "NOW()",
|
||||
}, common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
// 条件更新(多条件自动 AND 连接)
|
||||
affected := database.Update("user", common.Map{
|
||||
"status": 0,
|
||||
}, common.Map{
|
||||
"age[<]": 18,
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
fmt.Println("更新的记录数:", affected)
|
||||
```
|
||||
|
||||
### Upsert操作
|
||||
|
||||
Upsert(插入或更新):如果记录存在则更新,不存在则插入。
|
||||
|
||||
```go
|
||||
// Upsert 操作(使用 Slice 格式)
|
||||
affected := database.Upsert("user",
|
||||
common.Map{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"email": "zhang@example.com",
|
||||
"login_count": 1,
|
||||
},
|
||||
common.Slice{"id"}, // 唯一键(用于冲突检测)
|
||||
common.Slice{"name", "email", "login_count"}, // 冲突时更新的字段
|
||||
)
|
||||
|
||||
// MySQL 生成:
|
||||
// INSERT INTO user (id,name,email,login_count) VALUES (?,?,?,?)
|
||||
// ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email), login_count=VALUES(login_count)
|
||||
|
||||
// PostgreSQL 生成:
|
||||
// INSERT INTO "user" (id,name,email,login_count) VALUES ($1,$2,$3,$4)
|
||||
// ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, email=EXCLUDED.email, login_count=EXCLUDED.login_count
|
||||
|
||||
// 使用 [#] 标记直接 SQL 更新
|
||||
affected := database.Upsert("user",
|
||||
common.Map{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"login_count[#]": "login_count + 1", // 直接 SQL 表达式
|
||||
},
|
||||
common.Slice{"id"},
|
||||
common.Slice{"name", "login_count"},
|
||||
)
|
||||
|
||||
// 也支持可变参数形式
|
||||
affected := database.Upsert("user",
|
||||
common.Map{"id": 1, "name": "张三"},
|
||||
common.Slice{"id"},
|
||||
"name", "email", // 可变参数
|
||||
)
|
||||
```
|
||||
|
||||
### 删除(Delete)
|
||||
|
||||
```go
|
||||
// 根据ID删除
|
||||
affected := database.Delete("user", common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
// 条件删除(多条件自动 AND 连接)
|
||||
affected := database.Delete("user", common.Map{
|
||||
"status": 0,
|
||||
"created_time[<]": "2023-01-01",
|
||||
})
|
||||
|
||||
fmt.Println("删除的记录数:", affected)
|
||||
```
|
||||
|
||||
## 链式查询构建器
|
||||
|
||||
HoTimeDB提供了链式查询构建器,让查询更加直观:
|
||||
|
||||
```go
|
||||
// 基本链式查询
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
And("age[>]", 18).
|
||||
Order("created_time DESC").
|
||||
Limit(10, 20). // offset, limit
|
||||
Select()
|
||||
|
||||
// 链式获取单条记录
|
||||
user := database.Table("user").
|
||||
Where("id", 1).
|
||||
Get()
|
||||
|
||||
// 链式更新
|
||||
affected := database.Table("user").
|
||||
Where("id", 1).
|
||||
Update(common.Map{
|
||||
"name": "新名称",
|
||||
"updated_time[#]": "NOW()",
|
||||
})
|
||||
|
||||
// 链式删除
|
||||
affected := database.Table("user").
|
||||
Where("status", 0).
|
||||
Delete()
|
||||
|
||||
// 链式统计
|
||||
count := database.Table("user").
|
||||
Where("status", 1).
|
||||
Count()
|
||||
```
|
||||
|
||||
### 链式条件组合
|
||||
|
||||
```go
|
||||
// 复杂条件组合
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
And("age[>=]", 18).
|
||||
Or(common.Map{
|
||||
"level[>]": 5,
|
||||
"vip": 1,
|
||||
}).
|
||||
Order("created_time DESC", "id ASC").
|
||||
Group("department").
|
||||
Having(common.Map{"COUNT(*).[>]": 5}). // 新增 HAVING 支持
|
||||
Limit(0, 20).
|
||||
Offset(10). // 新增独立 OFFSET 支持
|
||||
Select("id,name,email,age")
|
||||
```
|
||||
|
||||
## 条件查询语法
|
||||
|
||||
HoTimeDB支持丰富的条件查询语法,类似于Medoo:
|
||||
|
||||
### 基本比较
|
||||
|
||||
```go
|
||||
// 等于
|
||||
"id": 1
|
||||
|
||||
// 不等于
|
||||
"id[!]": 1
|
||||
|
||||
// 大于
|
||||
"age[>]": 18
|
||||
|
||||
// 大于等于
|
||||
"age[>=]": 18
|
||||
|
||||
// 小于
|
||||
"age[<]": 60
|
||||
|
||||
// 小于等于
|
||||
"age[<=]": 60
|
||||
```
|
||||
|
||||
### 模糊查询
|
||||
|
||||
```go
|
||||
// LIKE %keyword%
|
||||
"name[~]": "张"
|
||||
|
||||
// LIKE keyword% (右边任意)
|
||||
"name[~!]": "张"
|
||||
|
||||
// LIKE %keyword (左边任意)
|
||||
"name[!~]": "san"
|
||||
|
||||
// 手动LIKE(需要手动添加%)
|
||||
"name[~~]": "%张%"
|
||||
```
|
||||
|
||||
### 区间查询
|
||||
|
||||
```go
|
||||
// BETWEEN
|
||||
"age[<>]": []int{18, 60}
|
||||
|
||||
// NOT BETWEEN
|
||||
"age[><]": []int{18, 25}
|
||||
```
|
||||
|
||||
### IN查询
|
||||
|
||||
```go
|
||||
// IN
|
||||
"id": []int{1, 2, 3, 4, 5}
|
||||
|
||||
// NOT IN
|
||||
"id[!]": []int{1, 2, 3}
|
||||
```
|
||||
|
||||
### NULL查询
|
||||
|
||||
```go
|
||||
// IS NULL
|
||||
"deleted_at": nil
|
||||
|
||||
// IS NOT NULL
|
||||
"deleted_at[!]": nil
|
||||
```
|
||||
|
||||
### 直接SQL
|
||||
|
||||
```go
|
||||
// 直接插入SQL表达式(注意防注入)
|
||||
"created_time[#]": "> DATE_SUB(NOW(), INTERVAL 1 DAY)"
|
||||
|
||||
// 字段直接赋值(不使用参数化查询)
|
||||
"update_time[#]": "NOW()"
|
||||
|
||||
// 直接SQL片段
|
||||
"[##]": "user.status = 1 AND user.level > 0"
|
||||
```
|
||||
|
||||
## JOIN操作
|
||||
|
||||
### 链式JOIN
|
||||
|
||||
```go
|
||||
// LEFT JOIN
|
||||
users := database.Table("user").
|
||||
LeftJoin("profile", "user.id = profile.user_id").
|
||||
LeftJoin("department", "user.dept_id = department.id").
|
||||
Where("user.status", 1).
|
||||
Select("user.*, profile.avatar, department.name AS dept_name")
|
||||
|
||||
// RIGHT JOIN
|
||||
users := database.Table("user").
|
||||
RightJoin("order", "user.id = order.user_id").
|
||||
Select()
|
||||
|
||||
// INNER JOIN
|
||||
users := database.Table("user").
|
||||
InnerJoin("profile", "user.id = profile.user_id").
|
||||
Select()
|
||||
|
||||
// FULL JOIN
|
||||
users := database.Table("user").
|
||||
FullJoin("profile", "user.id = profile.user_id").
|
||||
Select()
|
||||
```
|
||||
|
||||
### 传统JOIN语法
|
||||
|
||||
```go
|
||||
users := database.Select("user",
|
||||
common.Slice{
|
||||
common.Map{"[>]profile": "user.id = profile.user_id"},
|
||||
common.Map{"[>]department": "user.dept_id = department.id"},
|
||||
},
|
||||
"user.*, profile.avatar, department.name AS dept_name",
|
||||
common.Map{
|
||||
"user.status": 1,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### JOIN类型说明
|
||||
|
||||
- `[>]`: LEFT JOIN
|
||||
- `[<]`: RIGHT JOIN
|
||||
- `[><]`: INNER JOIN
|
||||
- `[<>]`: FULL JOIN
|
||||
|
||||
## 分页查询
|
||||
|
||||
### 基本分页
|
||||
|
||||
```go
|
||||
// 设置分页:页码3,每页20条
|
||||
users := database.Page(3, 20).PageSelect("user", "*", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 链式分页
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
Page(2, 15). // 第2页,每页15条
|
||||
Select()
|
||||
```
|
||||
|
||||
### 分页信息获取
|
||||
|
||||
```go
|
||||
// 获取总数
|
||||
total := database.Count("user", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 计算分页信息
|
||||
page := 2
|
||||
pageSize := 20
|
||||
offset := (page - 1) * pageSize
|
||||
totalPages := (total + pageSize - 1) / pageSize
|
||||
|
||||
fmt.Printf("总记录数: %d, 总页数: %d, 当前页: %d\n", total, totalPages, page)
|
||||
```
|
||||
|
||||
## 聚合函数
|
||||
|
||||
### 计数
|
||||
|
||||
```go
|
||||
// 总数统计
|
||||
total := database.Count("user")
|
||||
|
||||
// 条件统计
|
||||
activeUsers := database.Count("user", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// JOIN统计
|
||||
count := database.Count("user",
|
||||
common.Slice{
|
||||
common.Map{"[>]profile": "user.id = profile.user_id"},
|
||||
},
|
||||
common.Map{
|
||||
"user.status": 1,
|
||||
"profile.verified": 1,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 求和
|
||||
|
||||
```go
|
||||
// 基本求和
|
||||
totalAmount := database.Sum("order", "amount")
|
||||
|
||||
// 条件求和
|
||||
paidAmount := database.Sum("order", "amount", common.Map{
|
||||
"status": "paid",
|
||||
"created_time[>]": "2023-01-01",
|
||||
})
|
||||
```
|
||||
|
||||
### 平均值
|
||||
|
||||
```go
|
||||
// 基本平均值
|
||||
avgAge := database.Avg("user", "age")
|
||||
|
||||
// 条件平均值
|
||||
avgAge := database.Avg("user", "age", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
```
|
||||
|
||||
### 最大值/最小值
|
||||
|
||||
```go
|
||||
// 最大值
|
||||
maxAge := database.Max("user", "age")
|
||||
|
||||
// 最小值
|
||||
minAge := database.Min("user", "age")
|
||||
|
||||
// 条件最大值
|
||||
maxBalance := database.Max("user", "balance", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
```
|
||||
|
||||
## 事务处理
|
||||
|
||||
```go
|
||||
// 事务操作
|
||||
success := database.Action(func(tx db.HoTimeDB) bool {
|
||||
// 在事务中执行多个操作
|
||||
|
||||
// 扣减用户余额
|
||||
affected1 := tx.Update("user", common.Map{
|
||||
"balance[#]": "balance - 100",
|
||||
}, common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
if affected1 == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
orderId := tx.Insert("order", common.Map{
|
||||
"user_id": 1,
|
||||
"amount": 100,
|
||||
"status": "paid",
|
||||
"created_time[#]": "NOW()",
|
||||
})
|
||||
|
||||
if orderId == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
return true // 提交
|
||||
})
|
||||
|
||||
if success {
|
||||
fmt.Println("事务执行成功")
|
||||
} else {
|
||||
fmt.Println("事务回滚")
|
||||
fmt.Println("错误:", database.LastErr.GetError())
|
||||
}
|
||||
```
|
||||
|
||||
## 缓存机制
|
||||
|
||||
HoTimeDB集成了缓存功能,可以自动缓存查询结果:
|
||||
|
||||
### 缓存配置
|
||||
|
||||
```go
|
||||
import "code.hoteas.com/golang/hotime/cache"
|
||||
|
||||
// 设置缓存
|
||||
database.HoTimeCache = &cache.HoTimeCache{
|
||||
// 缓存配置
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存行为
|
||||
|
||||
- 查询操作会自动检查缓存
|
||||
- 增删改操作会自动清除相关缓存
|
||||
- 缓存键格式:`表名:查询MD5`
|
||||
- `cached`表不会被缓存
|
||||
|
||||
### 缓存清理
|
||||
|
||||
```go
|
||||
// 手动清除表缓存
|
||||
database.HoTimeCache.Db("user*", nil) // 清除user表所有缓存
|
||||
```
|
||||
|
||||
## PostgreSQL支持
|
||||
|
||||
HoTimeDB 支持 PostgreSQL 数据库,自动处理语法差异。
|
||||
|
||||
### PostgreSQL 配置
|
||||
|
||||
```go
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/db"
|
||||
_ "github.com/lib/pq" // PostgreSQL 驱动
|
||||
)
|
||||
|
||||
database := &db.HoTimeDB{
|
||||
Type: "postgres", // 设置数据库类型为 postgres
|
||||
Prefix: "app_",
|
||||
}
|
||||
|
||||
database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
|
||||
dsn := "host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable"
|
||||
master, _ = sql.Open("postgres", dsn)
|
||||
return master, master
|
||||
})
|
||||
```
|
||||
|
||||
### 主要差异
|
||||
|
||||
| 特性 | MySQL | PostgreSQL |
|
||||
|------|-------|------------|
|
||||
| 标识符引号 | \`name\` | "name" |
|
||||
| 占位符 | ? | $1, $2, $3... |
|
||||
| Upsert | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE |
|
||||
|
||||
所有这些差异由框架自动处理,无需手动调整代码。
|
||||
|
||||
## 高级特性
|
||||
|
||||
### 调试模式
|
||||
|
||||
```go
|
||||
// 设置调试模式
|
||||
database.Mode = 2
|
||||
|
||||
// 查看最后执行的SQL
|
||||
fmt.Println("最后的SQL:", database.LastQuery)
|
||||
fmt.Println("参数:", database.LastData)
|
||||
fmt.Println("错误:", database.LastErr.GetError())
|
||||
```
|
||||
|
||||
### 主从分离
|
||||
|
||||
```go
|
||||
func createConnection() (master, slave *sql.DB) {
|
||||
// 主库连接
|
||||
master, _ = sql.Open("mysql", "user:password@tcp(master:3306)/database")
|
||||
|
||||
// 从库连接
|
||||
slave, _ = sql.Open("mysql", "user:password@tcp(slave:3306)/database")
|
||||
|
||||
return master, slave
|
||||
}
|
||||
|
||||
database.SetConnect(createConnection)
|
||||
// 查询会自动使用从库,增删改使用主库
|
||||
```
|
||||
|
||||
### 原生SQL执行
|
||||
|
||||
```go
|
||||
// 执行查询SQL
|
||||
results := database.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1)
|
||||
|
||||
// 执行更新SQL
|
||||
result, err := database.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1)
|
||||
if err.GetError() == nil {
|
||||
affected, _ := result.RowsAffected()
|
||||
fmt.Println("影响行数:", affected)
|
||||
}
|
||||
```
|
||||
|
||||
## 特殊语法详解
|
||||
|
||||
### 条件标记符说明
|
||||
|
||||
| 标记符 | 功能 | 示例 | 生成SQL |
|
||||
|--------|------|------|---------|
|
||||
| `[>]` | 大于 | `"age[>]": 18` | `age > 18` |
|
||||
| `[<]` | 小于 | `"age[<]": 60` | `age < 60` |
|
||||
| `[>=]` | 大于等于 | `"age[>=]": 18` | `age >= 18` |
|
||||
| `[<=]` | 小于等于 | `"age[<=]": 60` | `age <= 60` |
|
||||
| `[!]` | 不等于/NOT IN | `"id[!]": 1` | `id != 1` |
|
||||
| `[~]` | LIKE模糊查询 | `"name[~]": "张"` | `name LIKE '%张%'` |
|
||||
| `[!~]` | 左模糊 | `"name[!~]": "张"` | `name LIKE '%张'` |
|
||||
| `[~!]` | 右模糊 | `"name[~!]": "张"` | `name LIKE '张%'` |
|
||||
| `[~~]` | 手动LIKE | `"name[~~]": "%张%"` | `name LIKE '%张%'` |
|
||||
| `[<>]` | BETWEEN | `"age[<>]": [18,60]` | `age BETWEEN 18 AND 60` |
|
||||
| `[><]` | NOT BETWEEN | `"age[><]": [18,25]` | `age NOT BETWEEN 18 AND 25` |
|
||||
| `[#]` | 直接SQL | `"time[#]": "NOW()"` | `time = NOW()` |
|
||||
| `[##]` | SQL片段 | `"[##]": "a > b"` | `a > b` |
|
||||
| `[#!]` | 不等于直接SQL | `"status[#!]": "1"` | `status != 1` |
|
||||
| `[!#]` | 不等于直接SQL | `"status[!#]": "1"` | `status != 1` |
|
||||
|
||||
### 特殊关键字(支持大小写)
|
||||
|
||||
| 关键字 | 功能 | 示例 |
|
||||
|--------|------|------|
|
||||
| `ORDER` / `order` | 排序 | `"ORDER": "id DESC"` |
|
||||
| `GROUP` / `group` | 分组 | `"GROUP": "dept_id"` |
|
||||
| `LIMIT` / `limit` | 限制 | `"LIMIT": 10` |
|
||||
| `OFFSET` / `offset` | 偏移 | `"OFFSET": 20` |
|
||||
| `HAVING` / `having` | 分组过滤 | `"HAVING": Map{"cnt[>]": 5}` |
|
||||
| `DISTINCT` / `distinct` | 去重 | 在 SELECT 中使用 |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 多条件查询需要用 AND 包装吗?
|
||||
A1: 不再需要!现在多条件会自动用 AND 连接。当然,使用 `AND` 包装仍然有效(向后兼容)。
|
||||
|
||||
### Q2: 如何处理事务中的错误?
|
||||
A2: 在 `Action` 函数中返回 `false` 即可触发回滚,所有操作都会被撤销。
|
||||
|
||||
### Q3: 缓存何时会被清除?
|
||||
A3: 执行 `Insert`、`Update`、`Delete`、`Upsert`、`Inserts` 操作时会自动清除对应表的缓存。
|
||||
|
||||
### Q4: 如何执行复杂的原生SQL?
|
||||
A4: 使用 `Query` 方法执行查询,使用 `Exec` 方法执行更新操作。
|
||||
|
||||
### Q5: 主从分离如何工作?
|
||||
A5: 查询操作自动使用从库(如果配置了),增删改操作使用主库。
|
||||
|
||||
### Q6: 如何处理NULL值?
|
||||
A6: 使用 `nil` 作为值,查询时使用 `"field": nil` 表示 `IS NULL`。
|
||||
|
||||
### Q7: PostgreSQL 和 MySQL 语法有区别吗?
|
||||
A7: 框架会自动处理差异(占位符、引号等),代码无需修改。
|
||||
|
||||
---
|
||||
|
||||
*文档版本: 2.0*
|
||||
*最后更新: 2026年1月*
|
||||
|
||||
> 本文档基于HoTimeDB源码分析生成,如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念,但根据Golang语言特性进行了适配和优化。
|
||||
|
||||
**更多参考:**
|
||||
- [HoTimeDB API 参考](HoTimeDB_API参考.md) - API 速查手册
|
||||
@@ -0,0 +1,302 @@
|
||||
# HoTime 快速上手指南
|
||||
|
||||
5 分钟入门 HoTime 框架。
|
||||
|
||||
## 安装
|
||||
|
||||
```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"
|
||||
}
|
||||
},
|
||||
"cache": {
|
||||
"memory": {
|
||||
"db": true,
|
||||
"session": true,
|
||||
"timeout": 7200
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `port` | 服务端口 |
|
||||
| `mode` | 0=生产, 1=测试, 2=开发(输出SQL) |
|
||||
| `tpt` | 静态文件目录 |
|
||||
| `db` | 数据库配置 |
|
||||
| `cache` | 缓存配置 |
|
||||
|
||||
## 路由系统
|
||||
|
||||
HoTime 使用三层路由结构:`模块/控制器/方法`
|
||||
|
||||
```go
|
||||
appIns.Run(Router{
|
||||
"模块名": {
|
||||
"控制器名": {
|
||||
"方法名": func(that *Context) {
|
||||
// 处理逻辑
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 获取请求参数
|
||||
|
||||
```go
|
||||
// GET/POST 参数
|
||||
name := that.Req.FormValue("name")
|
||||
|
||||
// JSON Body
|
||||
that.Req.ParseForm()
|
||||
data := that.Req.PostForm
|
||||
|
||||
// 获取路径参数
|
||||
module := that.RouterPath[0] // 模块
|
||||
controller := that.RouterPath[1] // 控制器
|
||||
action := that.RouterPath[2] // 方法
|
||||
```
|
||||
|
||||
### 响应数据
|
||||
|
||||
```go
|
||||
// 标准 JSON 响应
|
||||
that.Display(0, Map{"data": "value"}) // 成功 code=0
|
||||
that.Display(1, "错误信息") // 失败 code=1
|
||||
|
||||
// 直接写入
|
||||
that.Resp.Write([]byte("raw data"))
|
||||
```
|
||||
|
||||
## 中间件
|
||||
|
||||
```go
|
||||
// 全局中间件(请求拦截)
|
||||
appIns.SetConnectListener(func(that *Context) bool {
|
||||
// 检查登录状态
|
||||
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") // 获取
|
||||
that.Session("user_id", nil) // 删除
|
||||
|
||||
// 通用缓存
|
||||
that.Cache("key", "value") // 设置
|
||||
data := that.Cache("key") // 获取
|
||||
that.Cache("key", nil) // 删除
|
||||
```
|
||||
|
||||
三级缓存自动运作:**Memory → Redis → Database**
|
||||
|
||||
## 数据库操作(简要)
|
||||
|
||||
### 基础 CRUD
|
||||
|
||||
```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})
|
||||
|
||||
// 更新
|
||||
rows := that.Db.Update("user", Map{"name": "new"}, Map{"id": 1})
|
||||
|
||||
// 删除
|
||||
rows := that.Db.Delete("user", Map{"id": 1})
|
||||
```
|
||||
|
||||
### 链式查询
|
||||
|
||||
```go
|
||||
users := that.Db.Table("user").
|
||||
LeftJoin("order", "user.id=order.user_id").
|
||||
Where("status", 1).
|
||||
And("age[>]", 18).
|
||||
Order("id DESC").
|
||||
Page(1, 10).
|
||||
Select("*")
|
||||
```
|
||||
|
||||
### 条件语法速查
|
||||
|
||||
| 语法 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `key` | 等于 | `"id": 1` |
|
||||
| `key[>]` | 大于 | `"age[>]": 18` |
|
||||
| `key[<]` | 小于 | `"age[<]": 60` |
|
||||
| `key[>=]` | 大于等于 | `"age[>=]": 18` |
|
||||
| `key[<=]` | 小于等于 | `"age[<=]": 60` |
|
||||
| `key[!]` | 不等于 | `"status[!]": 0` |
|
||||
| `key[~]` | LIKE | `"name[~]": "test"` |
|
||||
| `key[<>]` | BETWEEN | `"age[<>]": Slice{18, 60}` |
|
||||
| `key` | IN | `"id": Slice{1, 2, 3}` |
|
||||
|
||||
### 事务
|
||||
|
||||
```go
|
||||
success := that.Db.Action(func(tx db.HoTimeDB) bool {
|
||||
tx.Update("user", Map{"balance[#]": "balance - 100"}, Map{"id": 1})
|
||||
tx.Insert("order", Map{"user_id": 1, "amount": 100})
|
||||
return true // 返回 true 提交,false 回滚
|
||||
})
|
||||
```
|
||||
|
||||
> **更多数据库操作**:参见 [HoTimeDB 使用说明](HoTimeDB_使用说明.md)
|
||||
|
||||
## 扩展功能
|
||||
|
||||
| 功能 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 微信支付/公众号/小程序 | `dri/wechat/` | 微信全套 SDK |
|
||||
| 阿里云服务 | `dri/aliyun/` | 企业认证等 |
|
||||
| 腾讯云服务 | `dri/tencent/` | 企业认证等 |
|
||||
| 文件上传 | `dri/upload/` | 文件上传处理 |
|
||||
| 文件下载 | `dri/download/` | 文件下载处理 |
|
||||
| MongoDB | `dri/mongodb/` | MongoDB 驱动 |
|
||||
| RSA 加解密 | `dri/rsa/` | RSA 加解密工具 |
|
||||
|
||||
## 完整示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
. "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 that.RouterPath[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.Req.FormValue("phone")
|
||||
password := that.Req.FormValue("password")
|
||||
|
||||
user := that.Db.Get("user", "*", Map{
|
||||
"phone": phone,
|
||||
"password": password,
|
||||
})
|
||||
|
||||
if user == nil {
|
||||
that.Display(1, "账号或密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
that.Session("user_id", user.GetInt64("id"))
|
||||
that.Display(0, Map{"user": user})
|
||||
},
|
||||
|
||||
"info": func(that *Context) {
|
||||
userId := that.Session("user_id").ToInt64()
|
||||
user := that.Db.Get("user", "*", Map{"id": userId})
|
||||
that.Display(0, Map{"user": user})
|
||||
},
|
||||
|
||||
"list": func(that *Context) {
|
||||
page := that.Req.FormValue("page")
|
||||
if page == "" {
|
||||
page = "1"
|
||||
}
|
||||
|
||||
users := that.Db.Table("user").
|
||||
Where("status", 1).
|
||||
Order("id DESC").
|
||||
Page(ObjToInt(page), 10).
|
||||
Select("id,name,phone,created_at")
|
||||
|
||||
total := that.Db.Count("user", Map{"status": 1})
|
||||
|
||||
that.Display(0, Map{
|
||||
"list": users,
|
||||
"total": total,
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**下一步**:
|
||||
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) - 完整数据库教程
|
||||
- [HoTimeDB API 参考](HoTimeDB_API参考.md) - API 速查手册
|
||||
@@ -1,6 +1,7 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -21,6 +22,93 @@ func (that *baiduMap) Init(Ak string) {
|
||||
//query
|
||||
}
|
||||
|
||||
// from 源坐标类型:
|
||||
// 1:GPS标准坐标;
|
||||
// 2:搜狗地图坐标;
|
||||
// 3:火星坐标(gcj02),即高德地图、腾讯地图和MapABC等地图使用的坐标;
|
||||
// 4:3中列举的地图坐标对应的墨卡托平面坐标;
|
||||
// 5:百度地图采用的经纬度坐标(bd09ll);
|
||||
// 6:百度地图采用的墨卡托平面坐标(bd09mc);
|
||||
// 7:图吧地图坐标;
|
||||
// 8:51地图坐标;
|
||||
// int 1 1 否
|
||||
// to
|
||||
// 目标坐标类型:
|
||||
// 3:火星坐标(gcj02),即高德地图、腾讯地图及MapABC等地图使用的坐标;
|
||||
// 5:百度地图采用的经纬度坐标(bd09ll);
|
||||
// 6:百度地图采用的墨卡托平面坐标(bd09mc);
|
||||
func (that *baiduMap) Geoconv(latlngs []Map, from, to int) (Slice, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
latlngsStr := ""
|
||||
for _, v := range latlngs {
|
||||
if latlngsStr != "" {
|
||||
latlngsStr = latlngsStr + ";" + v.GetString("lng") + "," + v.GetString("lat")
|
||||
} else {
|
||||
latlngsStr = v.GetString("lng") + "," + v.GetString("lat")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
url := "https://api.map.baidu.com/geoconv/v1/?from=" + ObjToStr(from) + "&to=" + ObjToStr(to) + "&ak=" + that.Ak + "&coords=" + latlngsStr
|
||||
reqest, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Println(string(body))
|
||||
data := ObjToMap(string(body))
|
||||
if data.GetCeilInt64("status") != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data.GetSlice("result"), err
|
||||
}
|
||||
|
||||
func (that *baiduMap) GetAddress(lat string, lng string) (string, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
url := "https://api.map.baidu.com/reverse_geocoding/v3/?ak=" + that.Ak + "&output=json&location=" + lat + "," + lng
|
||||
reqest, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//fmt.Println(string(body))
|
||||
|
||||
return string(body), err
|
||||
|
||||
}
|
||||
|
||||
// GetPosition 获取定位列表
|
||||
func (that *baiduMap) GetPosition(name string, region string) (string, error) {
|
||||
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ func (that *dingdongyun) SendTz(umoblie []string, tpt string, data map[string]st
|
||||
return that.send(that.TzUrl, umobleStr, tpt)
|
||||
}
|
||||
|
||||
//发送短信
|
||||
// 发送短信
|
||||
func (that *dingdongyun) send(mUrl string, umoblie string, content string) (bool, error) {
|
||||
|
||||
data_send_sms_yzm := url.Values{"apikey": {that.ApiKey}, "mobile": {umoblie}, "content": {content}}
|
||||
@@ -74,7 +74,7 @@ func (that *dingdongyun) send(mUrl string, umoblie string, content string) (bool
|
||||
return true, nil
|
||||
}
|
||||
|
||||
//调用url发送短信的连接
|
||||
// 调用url发送短信的连接
|
||||
func (that *dingdongyun) httpsPostForm(url string, data url.Values) (string, error) {
|
||||
resp, err := http.PostForm(url, data)
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ func Down(url, path, name string, e ...*Error) bool {
|
||||
}
|
||||
out, err := os.Create(path + name)
|
||||
|
||||
if err != nil && e[0] != nil {
|
||||
if err != nil && len(e) != 0 {
|
||||
e[0].SetError(err)
|
||||
return false
|
||||
}
|
||||
defer out.Close()
|
||||
resp, err := http.Get(url)
|
||||
if err != nil && e[0] != nil {
|
||||
if err != nil && len(e) != 0 {
|
||||
e[0].SetError(err)
|
||||
return false
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func Down(url, path, name string, e ...*Error) bool {
|
||||
|
||||
pix, err := ioutil.ReadAll(resp.Body)
|
||||
_, err = io.Copy(out, bytes.NewReader(pix))
|
||||
if err != nil && e[0] != nil {
|
||||
if err != nil && len(e) != 0 {
|
||||
e[0].SetError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"context"
|
||||
"fmt"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type MongoDb struct {
|
||||
Client *mongo.Client
|
||||
Ctx context.Context
|
||||
DataBase *mongo.Database
|
||||
Connect *mongo.Collection
|
||||
LastErr error
|
||||
}
|
||||
|
||||
func GetMongoDb(database, url string) (*MongoDb, error) {
|
||||
db := MongoDb{}
|
||||
clientOptions := options.Client().ApplyURI(url)
|
||||
|
||||
db.Ctx = context.TODO()
|
||||
// Connect to MongoDb
|
||||
var err error
|
||||
db.Client, err = mongo.Connect(db.Ctx, clientOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check the connection
|
||||
err = db.Client.Ping(db.Ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println("Connected to MongoDb!")
|
||||
//databases, err := db.Client.ListDatabaseNames(db.Ctx, bson.M{})
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//fmt.Println(databases)
|
||||
db.DataBase = db.Client.Database(database)
|
||||
return &db, nil
|
||||
|
||||
}
|
||||
|
||||
func (that *MongoDb) Insert(table string, data interface{}) string {
|
||||
collection := that.DataBase.Collection(table)
|
||||
re, err := collection.InsertOne(that.Ctx, data)
|
||||
if err != nil {
|
||||
that.LastErr = err
|
||||
return ""
|
||||
}
|
||||
return ObjToStr(re.InsertedID)
|
||||
}
|
||||
|
||||
func (that *MongoDb) InsertMany(table string, data ...interface{}) Slice {
|
||||
collection := that.DataBase.Collection(table)
|
||||
re, err := collection.InsertMany(that.Ctx, data)
|
||||
if err != nil {
|
||||
that.LastErr = err
|
||||
return Slice{}
|
||||
}
|
||||
|
||||
return ObjToSlice(re.InsertedIDs)
|
||||
|
||||
}
|
||||
|
||||
func (that *MongoDb) Update(table string, data Map, where Map) int64 {
|
||||
collection := that.DataBase.Collection(table)
|
||||
re, err := collection.UpdateMany(that.Ctx, where, data)
|
||||
|
||||
if err != nil {
|
||||
that.LastErr = err
|
||||
return 0
|
||||
}
|
||||
|
||||
return re.ModifiedCount
|
||||
|
||||
}
|
||||
|
||||
func (that *MongoDb) Delete(table string, where Map) int64 {
|
||||
collection := that.DataBase.Collection(table)
|
||||
re, err := collection.DeleteMany(that.Ctx, where)
|
||||
|
||||
if err != nil {
|
||||
that.LastErr = err
|
||||
return 0
|
||||
}
|
||||
|
||||
return re.DeletedCount
|
||||
|
||||
}
|
||||
|
||||
func (that *MongoDb) Get(table string, where Map) Map {
|
||||
results := []Map{}
|
||||
var cursor *mongo.Cursor
|
||||
var err error
|
||||
collection := that.DataBase.Collection(table)
|
||||
if cursor, err = collection.Find(that.Ctx, where, options.Find().SetSkip(0), options.Find().SetLimit(2)); err != nil {
|
||||
that.LastErr = err
|
||||
return nil
|
||||
}
|
||||
//延迟关闭游标
|
||||
defer func() {
|
||||
if err := cursor.Close(that.Ctx); err != nil {
|
||||
that.LastErr = err
|
||||
|
||||
}
|
||||
}()
|
||||
//这里的结果遍历可以使用另外一种更方便的方式:
|
||||
if err = cursor.All(that.Ctx, &results); err != nil {
|
||||
that.LastErr = err
|
||||
return nil
|
||||
}
|
||||
if len(results) > 0 {
|
||||
|
||||
return results[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (that MongoDb) Select(table string, where Map, page, pageRow int64) []Map {
|
||||
page = (page - 1) * pageRow
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
results := []Map{}
|
||||
var cursor *mongo.Cursor
|
||||
var err error
|
||||
collection := that.DataBase.Collection(table)
|
||||
if cursor, err = collection.Find(that.Ctx, where, options.Find().SetSkip(page), options.Find().SetLimit(pageRow)); err != nil {
|
||||
that.LastErr = err
|
||||
return results
|
||||
}
|
||||
//延迟关闭游标
|
||||
defer func() {
|
||||
if err := cursor.Close(that.Ctx); err != nil {
|
||||
that.LastErr = err
|
||||
|
||||
}
|
||||
}()
|
||||
//这里的结果遍历可以使用另外一种更方便的方式:
|
||||
|
||||
if err = cursor.All(that.Ctx, &results); err != nil {
|
||||
that.LastErr = err
|
||||
return results
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/silenceper/wechat/v2/officialaccount/oauth"
|
||||
)
|
||||
|
||||
//基于此文档开发
|
||||
//https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
// 基于此文档开发
|
||||
// https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
type h5Program struct {
|
||||
Memory *cache.Memory
|
||||
Config *h5config.Config
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//基于此文档开发
|
||||
//https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
// 基于此文档开发
|
||||
// https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
type wxpay struct {
|
||||
client *wechat.ClientV3
|
||||
ctx context.Context
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//高级加密标准(Adevanced Encryption Standard ,AES)
|
||||
|
||||
//16,24,32位字符串的话,分别对应AES-128,AES-192,AES-256 加密方法
|
||||
//key不能泄露
|
||||
var PwdKey = []byte("mif022h3g9geAHUHY432,:da1adag389")
|
||||
|
||||
//PKCS7 填充模式
|
||||
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
//Repeat()函数的功能是把切片[]byte{byte(padding)}复制padding个,然后合并成新的字节切片返回
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
//填充的反向操作,删除填充字符串
|
||||
func pKCS7UnPadding(origData []byte) ([]byte, error) {
|
||||
//获取数据长度
|
||||
length := len(origData)
|
||||
if length == 0 {
|
||||
return nil, errors.New("加密字符串错误!")
|
||||
} else {
|
||||
//获取填充字符串长度
|
||||
unpadding := int(origData[length-1])
|
||||
//截取切片,删除填充字节,并且返回明文
|
||||
return origData[:(length - unpadding)], nil
|
||||
}
|
||||
}
|
||||
|
||||
//实现加密
|
||||
func AesEcrypt(origData []byte, key []byte) ([]byte, error) {
|
||||
//创建加密算法实例
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//获取块的大小
|
||||
blockSize := block.BlockSize()
|
||||
//对数据进行填充,让数据长度满足需求
|
||||
origData = pKCS7Padding(origData, blockSize)
|
||||
//采用AES加密方法中CBC加密模式
|
||||
blocMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||
crypted := make([]byte, len(origData))
|
||||
//执行加密
|
||||
blocMode.CryptBlocks(crypted, origData)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
//实现解密
|
||||
func AesDeCrypt(cypted []byte, key []byte) (bytes []byte, err error) {
|
||||
//异常处理
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
//that.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
|
||||
fmt.Println(err)
|
||||
err = errors.New(common.ObjToStr(e))
|
||||
}
|
||||
}()
|
||||
//创建加密算法实例
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//获取块大小
|
||||
blockSize := block.BlockSize()
|
||||
//创建加密客户端实例
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
origData := make([]byte, len(cypted))
|
||||
//这个函数也可以用来解密
|
||||
blockMode.CryptBlocks(origData, cypted)
|
||||
//去除填充字符串
|
||||
origData, err = pKCS7UnPadding(origData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return origData, err
|
||||
}
|
||||
|
||||
//加密base64
|
||||
func EnPwdCode(pwd []byte) (string, error) {
|
||||
result, err := AesEcrypt(pwd, PwdKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(result), err
|
||||
}
|
||||
|
||||
//解密
|
||||
func DePwdCode(pwd string) ([]byte, error) {
|
||||
//解密base64字符串
|
||||
pwdByte, err := base64.StdEncoding.DecodeString(pwd)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
//执行AES解密
|
||||
return AesDeCrypt(pwdByte, PwdKey)
|
||||
|
||||
}
|
||||
|
||||
//func main() {
|
||||
// str := []byte("12fff我是ww.topgoer.com的站长枯藤")
|
||||
// pwd, _ := EnPwdCode(str)
|
||||
// bytes, _ := DePwdCode(pwd)
|
||||
// fmt.Println(string(bytes))
|
||||
//}
|
||||
@@ -1,110 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var ArticleCtr = Ctr{
|
||||
|
||||
"getdispatchs": func(that *Context) {
|
||||
|
||||
//判断类型
|
||||
tp := that.Req.FormValue("type")
|
||||
//判断类型
|
||||
data := Map{"del_flag": 0}
|
||||
if tp == "notify" {
|
||||
data["notify_id[!]"] = nil
|
||||
}
|
||||
|
||||
if tp == "policy" {
|
||||
data["policy_id[!]"] = nil
|
||||
}
|
||||
|
||||
if tp == "declare" {
|
||||
data["declare_id[!]"] = nil
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "GROUP": "dispatch_name"}
|
||||
} else {
|
||||
data["GROUP"] = "dispatch_name"
|
||||
}
|
||||
|
||||
res := that.Db.Select("article", "dispatch_name", data)
|
||||
that.Display(0, res)
|
||||
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "content[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["release_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["release_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
dispatchName := that.Req.FormValue("dispatch_name")
|
||||
if dispatchName != "" {
|
||||
data["dispatch_name"] = dispatchName
|
||||
}
|
||||
|
||||
//判断类型
|
||||
tp := that.Req.FormValue("type")
|
||||
if tp == "notify" {
|
||||
data["notify_id[!]"] = nil
|
||||
}
|
||||
|
||||
if tp == "policy" {
|
||||
data["policy_id[!]"] = nil
|
||||
}
|
||||
|
||||
if tp == "declare" {
|
||||
data["declare_id[!]"] = nil
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "release_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("article", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("article", "id,title,description,department_id,click_num+click_num_base AS click_num,"+
|
||||
"favorite_num_base+favorite_num AS favorite_num,dispatch_num,dispatch_name,prepare_date,release_time,expire_date,area_id,status,policy_id,declare_id,notify_id,dispatch_name,policy_level", data)
|
||||
for _, v := range res {
|
||||
|
||||
if v.GetCeilInt("notify_id") > 0 {
|
||||
v["notify"] = that.Db.Get("notify", "tag", Map{"id": v.GetCeilInt("notify_id")})
|
||||
}
|
||||
if v.GetCeilInt("policy_id") > 0 {
|
||||
v["policy"] = that.Db.Get("policy", "tag", Map{"id": v.GetCeilInt("policy_id")})
|
||||
}
|
||||
if v.GetCeilInt("declare_id") > 0 {
|
||||
v["declare"] = that.Db.Get("declare", "money_scope_min,money_scope_max,status", Map{"id": v.GetCeilInt("declare_id")})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/aliyun"
|
||||
"code.hoteas.com/golang/hotime/dri/tencent"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var CompanyCtr = Ctr{
|
||||
|
||||
"search": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords == "" {
|
||||
keywords = that.Req.FormValue("company_name")
|
||||
}
|
||||
if keywords == "" {
|
||||
keywords = that.Req.FormValue("name")
|
||||
}
|
||||
|
||||
if len(keywords) < 2 {
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
|
||||
res, err := aliyun.Company.GetCompanyList(keywords)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
if res.GetCeilInt64("status") != 200 {
|
||||
fmt.Println(err)
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, res.GetMap("data").GetSlice("list"))
|
||||
},
|
||||
"search_info": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
name := that.Req.FormValue("name")
|
||||
|
||||
if len(name) < 2 {
|
||||
that.Display(3, "找不到企业")
|
||||
return
|
||||
}
|
||||
|
||||
res, err := aliyun.Company.GetCompanyBaseInfo(name)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
that.Display(4, "查询失败")
|
||||
return
|
||||
}
|
||||
if res.GetBool("status") != true {
|
||||
fmt.Println(err)
|
||||
that.Display(4, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, res.GetMap("data"))
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
user := that.Db.Get("user", "*", Map{"id": that.Session("user_id").Data})
|
||||
if user == nil {
|
||||
that.Display(4, "找不到用户")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("company", "*", Map{"id": user.GetCeilInt("company_id")})
|
||||
if res == nil {
|
||||
that.Display(4, "找不到企业")
|
||||
return
|
||||
}
|
||||
//先不做限制
|
||||
//if res.GetCeilInt("salesman_id")!=that.Session("salesman_id").ToCeilInt(){
|
||||
// that.Display(4,"不是你的企业")
|
||||
// return
|
||||
//}
|
||||
|
||||
//res["technology_center_flag"] = ObjToSlice(res["technology_center_flag"])
|
||||
//res["engineering_center_flag"] = ObjToSlice(res["engineering_center_flag"])
|
||||
//res["engineering_laboratory_flag"] = ObjToSlice(res["engineering_laboratory_flag"])
|
||||
//res["key_laboratory_flag"] = ObjToSlice(res["key_laboratory_flag"])
|
||||
//res["industrial_design_center_flag"] = ObjToSlice(res["industrial_design_center_flag"])
|
||||
//
|
||||
//res["high_level_talents_flag1"] = ObjToSlice(res["high_level_talents_flag1"])
|
||||
//
|
||||
//res["tags"] = ObjToSlice(res["tags"])
|
||||
|
||||
res["technology_center_flag"] = strToArray(res.GetString("technology_center_flag"))
|
||||
res["engineering_center_flag"] = strToArray(res.GetString("engineering_center_flag"))
|
||||
res["engineering_laboratory_flag"] = strToArray(res.GetString("engineering_laboratory_flag"))
|
||||
res["key_laboratory_flag"] = strToArray(res.GetString("key_laboratory_flag"))
|
||||
res["industrial_design_center_flag"] = strToArray(res.GetString("industrial_design_center_flag"))
|
||||
res["high_level_talents_flag1"] = strToArray(res.GetString("high_level_talents_flag1"))
|
||||
res["tags"] = strToArray(res.GetString("tags"))
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
"edit": func(that *Context) {
|
||||
|
||||
//if that.Session("user_id").Data == nil {
|
||||
// that.Display(2, "没有登录")
|
||||
// return
|
||||
//}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
//统一社会信用代码
|
||||
social_code := that.Req.FormValue("social_code")
|
||||
if social_code == "" {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
//企业名称
|
||||
company_name := that.Req.FormValue("company_name")
|
||||
if company_name == "" {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
//手机号
|
||||
phone := that.Req.FormValue("phone")
|
||||
if phone == "" {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
//姓名
|
||||
name := that.Req.FormValue("user_name")
|
||||
if name == "" {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
//验证码
|
||||
err := auth(that, phone, company_name, name)
|
||||
if err != nil {
|
||||
that.Display(3, err.Error())
|
||||
return
|
||||
}
|
||||
//营业执照路径
|
||||
business_license := that.Req.FormValue("business_license")
|
||||
if business_license == "" {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
user_id := that.Session("user_id").Data
|
||||
user := that.Db.Get("user", "*", Map{"id": user_id})
|
||||
if user == nil {
|
||||
that.Display(1, "没有找到该用户")
|
||||
return
|
||||
}
|
||||
company := that.Db.Get("company", "*", Map{"AND": Map{"id": id, "user_id": user_id}})
|
||||
if company == nil {
|
||||
that.Display(4, "不是属于你的企业")
|
||||
return
|
||||
}
|
||||
//认证
|
||||
authentication_flag := 1
|
||||
//if user.GetCeilInt("authentication_flag") == 0 {
|
||||
// authentication_flag = 1
|
||||
//}
|
||||
//通过base64图片获取信息
|
||||
|
||||
that.Db.Update("user", Map{"name": name, "phone": phone, "authentication_flag": authentication_flag}, Map{"id": user_id})
|
||||
|
||||
that.Db.Update("company", Map{"social_code": social_code, "name": company_name, "phone": phone, "business_license": business_license, "modify_time[#]": "now()"}, Map{"id": id})
|
||||
|
||||
//赠送一张券
|
||||
data := Map{
|
||||
"user_id": user_id,
|
||||
"coupon_id": 1,
|
||||
"code_no": "SN" + time.Now().Format("20060102150405") + getSn(),
|
||||
"effective_start_time[#]": "NOW()",
|
||||
"effective_end_time": time.Now().AddDate(0, 6, 0).Format("2006-01-02 15:04:05"),
|
||||
"source_type": 2,
|
||||
"status": 0,
|
||||
"admin_id": user.GetCeilInt("admin_id"),
|
||||
"create_time[#]": "NOW()",
|
||||
}
|
||||
//先判断是否领取过 source_type=2 是通过认证赠送的券
|
||||
couponCount := that.Db.Count("coupon_user", Map{"AND": Map{"user_id": user_id, "source_type": 2}})
|
||||
if couponCount == 0 {
|
||||
that.Db.Insert("coupon_user", data)
|
||||
}
|
||||
that.Display(0, "认证成功")
|
||||
},
|
||||
//上传营业执照
|
||||
"upload": func(this *Context) {
|
||||
file := this.Req.FormValue("file")
|
||||
|
||||
if len(file) < 100 {
|
||||
this.Display(3, "图片上传错误")
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Println(uimg)
|
||||
btes, e := base64.StdEncoding.DecodeString(file[strings.Index(file, ",")+1:]) //成图片文件并把文件写入到buffer
|
||||
//btes, e := base64.StdEncoding.DecodeString(file) //成图片文件并把文件写入到buffer
|
||||
if e != nil {
|
||||
this.Display(3, "无法解析图片")
|
||||
return
|
||||
}
|
||||
|
||||
//uimgPath:=time.Now().Format(this.Config.GetString("uimgPath"))
|
||||
path := time.Now().Format(this.Config.GetString("wxFilePath"))
|
||||
os.MkdirAll(this.Config.GetString("tpt")+"/"+path, os.ModeDir)
|
||||
filePath := path + Md5(ObjToStr(time.Now().Unix())) + ".jpg"
|
||||
|
||||
err2 := ioutil.WriteFile(this.Config.GetString("tpt")+"/"+filePath, btes, 0666) //buffer输出到jpg文件中(不做处理,直接写到文件)
|
||||
if err2 != nil {
|
||||
this.Display(3, "图片保存失败")
|
||||
return
|
||||
}
|
||||
tp := this.Req.FormValue("type")
|
||||
|
||||
if tp == "company" {
|
||||
data := tencent.Tencent.OCRCOMPANY(file)
|
||||
c := ObjToMap(data)
|
||||
if c != nil {
|
||||
c = c.GetMap("Response")
|
||||
c["url"] = filePath
|
||||
|
||||
} else {
|
||||
c = Map{"url": filePath}
|
||||
}
|
||||
this.Display(0, c)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var CouponCtr = Ctr{
|
||||
|
||||
"search": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
user_id := that.Session("user_id").Data
|
||||
user := that.Db.Get("user", "*", Map{"id": user_id})
|
||||
if user == nil {
|
||||
that.Display(1, "没有找到该用户")
|
||||
return
|
||||
}
|
||||
|
||||
//0-待使用,1-已使用,2-已过期
|
||||
|
||||
status := ObjToInt(that.Req.FormValue("status"))
|
||||
var data = Map{
|
||||
"coupon_user.user_id": user_id,
|
||||
"coupon_user.state": 0,
|
||||
}
|
||||
if that.Req.FormValue("status") != "" {
|
||||
data.Put("coupon_user.status", status)
|
||||
}
|
||||
|
||||
specMap := Map{"AND": data}
|
||||
if status == 0 {
|
||||
specMap.Put("ORDER", "coupon_user.status ASC,coupon_user.effective_end_time ASC,coupon_user.create_time DESC")
|
||||
}
|
||||
if status == 1 {
|
||||
specMap.Put("ORDER", "coupon_user.use_time DESC")
|
||||
}
|
||||
if status == 2 {
|
||||
specMap.Put("ORDER", "coupon_user.effective_end_time DESC")
|
||||
}
|
||||
|
||||
count := that.Db.Count("coupon_user", Map{"AND": data})
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("coupon_user",
|
||||
Map{"[>]coupon": "coupon_user.coupon_id = coupon.id"},
|
||||
"coupon_user.code_no,coupon_user.effective_start_time,coupon_user.effective_end_time,coupon_user.status,"+
|
||||
"coupon.coupon_amount,coupon.coupon_type,coupon.name,"+
|
||||
"coupon.description",
|
||||
specMap,
|
||||
)
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,241 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var FavoriteCtr = Ctr{
|
||||
//关注
|
||||
"follow": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
policyId := ObjToInt(that.Req.FormValue("policy_id"))
|
||||
notifyId := ObjToInt(that.Req.FormValue("notify_id"))
|
||||
declareId := ObjToInt(that.Req.FormValue("declare_id"))
|
||||
providerId := ObjToInt(that.Req.FormValue("provider_id"))
|
||||
|
||||
favorite := Map{}
|
||||
data := Map{}
|
||||
if providerId != 0 {
|
||||
data = Map{"provider_id": providerId, "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 0
|
||||
data["type"] = 1
|
||||
|
||||
} else if notifyId != 0 {
|
||||
article := that.Db.Get("article", "id", Map{"notify_id": notifyId})
|
||||
if article != nil {
|
||||
data = Map{"notify_id": notifyId, "article_id": article.GetCeilInt("id"), "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 0
|
||||
data["type"] = 2
|
||||
}
|
||||
|
||||
} else if policyId != 0 {
|
||||
article := that.Db.Get("article", "id", Map{"policy_id": policyId})
|
||||
if article != nil {
|
||||
data = Map{"policy_id": policyId, "article_id": article.GetCeilInt("id"), "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 0
|
||||
data["type"] = 3
|
||||
}
|
||||
|
||||
} else if declareId != 0 {
|
||||
article := that.Db.Get("article", "id", Map{"declare_id": declareId})
|
||||
if article != nil {
|
||||
data = Map{"declare_id": declareId, "article_id": article.GetCeilInt("id"), "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 0
|
||||
data["type"] = 4
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) != 0 {
|
||||
isFavorite := int64(0)
|
||||
if favorite != nil {
|
||||
isFavorite = that.Db.Update("favorite", data, Map{"id": favorite.GetCeilInt("id")})
|
||||
} else {
|
||||
data["create_time[#]"] = "now()"
|
||||
isFavorite = that.Db.Insert("favorite", data)
|
||||
}
|
||||
|
||||
if isFavorite != 0 {
|
||||
if data.GetCeilInt("article_id") != 0 {
|
||||
that.Db.Update("article", Map{"favorite_num[#]": "favorite_num+1"}, Map{"id": data.GetCeilInt("article_id")})
|
||||
}
|
||||
if data.GetCeilInt("notify_id") != 0 {
|
||||
that.Db.Update("notify", Map{"favorite_num[#]": "favorite_num+1"}, Map{"id": data.GetCeilInt("notify_id")})
|
||||
}
|
||||
if data.GetCeilInt("policy_id") != 0 {
|
||||
that.Db.Update("policy", Map{"favorite_num[#]": "favorite_num+1"}, Map{"id": data.GetCeilInt("policy_id")})
|
||||
}
|
||||
|
||||
if data.GetCeilInt("declare_id") != 0 {
|
||||
that.Db.Update("declare", Map{"favorite_num[#]": "favorite_num+1"}, Map{"id": data.GetCeilInt("declare_id")})
|
||||
}
|
||||
|
||||
if data.GetCeilInt("provider_id") != 0 {
|
||||
that.Db.Update("provider", Map{"favorite_num[#]": "favorite_num+1"}, Map{"id": data.GetCeilInt("provider_id")})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, "关注成功")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(4, "找不到关注对象")
|
||||
return
|
||||
|
||||
},
|
||||
|
||||
//关注
|
||||
"unfollow": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
policyId := ObjToInt(that.Req.FormValue("policy_id"))
|
||||
notifyId := ObjToInt(that.Req.FormValue("notify_id"))
|
||||
declareId := ObjToInt(that.Req.FormValue("declare_id"))
|
||||
providerId := ObjToInt(that.Req.FormValue("provider_id"))
|
||||
|
||||
favorite := Map{}
|
||||
data := Map{}
|
||||
if providerId != 0 {
|
||||
data = Map{"provider_id": providerId, "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 1
|
||||
data["type"] = 1
|
||||
|
||||
} else if notifyId != 0 {
|
||||
article := that.Db.Get("article", "id", Map{"notify_id": notifyId})
|
||||
if article != nil {
|
||||
data = Map{"notify_id": notifyId, "article_id": article.GetCeilInt("id"), "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 1
|
||||
data["type"] = 2
|
||||
}
|
||||
} else if policyId != 0 {
|
||||
|
||||
article := that.Db.Get("article", "id", Map{"policy_id": policyId})
|
||||
if article != nil {
|
||||
data = Map{"policy_id": policyId, "article_id": article.GetCeilInt("id"), "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 1
|
||||
data["type"] = 3
|
||||
}
|
||||
|
||||
} else if declareId != 0 {
|
||||
article := that.Db.Get("article", "id", Map{"declare_id": declareId})
|
||||
if article != nil {
|
||||
data = Map{"declare_id": declareId, "article_id": article.GetCeilInt("id"), "user_id": that.Session("user_id").Data}
|
||||
favorite = that.Db.Get("favorite", "*", Map{"AND": data})
|
||||
data["modify_time[#]"] = "now()"
|
||||
data["del_flag"] = 1
|
||||
data["type"] = 4
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) != 0 && favorite != nil {
|
||||
|
||||
isFavorite := that.Db.Update("favorite", data, Map{"id": favorite.GetCeilInt("id")})
|
||||
|
||||
if isFavorite != 0 {
|
||||
if data.GetCeilInt("article_id") != 0 {
|
||||
that.Db.Update("article", Map{"favorite_num[#]": "favorite_num-1"}, Map{"id": data.GetCeilInt("article_id")})
|
||||
}
|
||||
if data.GetCeilInt("notify_id") != 0 {
|
||||
that.Db.Update("notify", Map{"favorite_num[#]": "favorite_num-1"}, Map{"id": data.GetCeilInt("notify_id")})
|
||||
}
|
||||
if data.GetCeilInt("policy_id") != 0 {
|
||||
that.Db.Update("policy", Map{"favorite_num[#]": "favorite_num-1"}, Map{"id": data.GetCeilInt("policy_id")})
|
||||
}
|
||||
|
||||
if data.GetCeilInt("declare_id") != 0 {
|
||||
that.Db.Update("declare", Map{"favorite_num[#]": "favorite_num-1"}, Map{"id": data.GetCeilInt("declare_id")})
|
||||
}
|
||||
|
||||
if data.GetCeilInt("provider_id") != 0 {
|
||||
that.Db.Update("provider", Map{"favorite_num[#]": "favorite_num-1"}, Map{"id": data.GetCeilInt("provider_id")})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, "取消关注成功")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, "没有关注")
|
||||
return
|
||||
},
|
||||
|
||||
"search": func(that *Context) {
|
||||
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
tp := ObjToInt(that.Req.FormValue("type"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "user_id": that.Session("user_id").Data}
|
||||
|
||||
if tp != 0 {
|
||||
data["type"] = tp
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "modify_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("favorite", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("favorite", "*", data)
|
||||
|
||||
for _, v := range res {
|
||||
if v.GetCeilInt64("policy_id") != 0 {
|
||||
v["policy"] = that.Db.Get("policy", "id,tag", Map{"id": v.GetCeilInt64("policy_id")})
|
||||
}
|
||||
if v.GetCeilInt64("declare_id") != 0 {
|
||||
v["declare"] = that.Db.Get("declare", "id,money_scope_min,money_scope_max,status", Map{"id": v.GetCeilInt64("declare_id")})
|
||||
}
|
||||
|
||||
if v.GetCeilInt64("notify_id") != 0 {
|
||||
v["notify"] = that.Db.Get("notify", "id,tag", Map{"id": v.GetCeilInt64("notify_id")})
|
||||
}
|
||||
|
||||
if v.GetCeilInt64("article_id") != 0 {
|
||||
v["article"] = that.Db.Get("article", "id,title,description,department_id,click_num+click_num_base AS click_num,"+
|
||||
"favorite_num_base+favorite_num AS favorite_num,dispatch_num,dispatch_name,prepare_date,release_time,expire_date,area_id,status", Map{"id": v.GetCeilInt64("article_id")})
|
||||
}
|
||||
|
||||
if v.GetCeilInt64("provider_id") != 0 {
|
||||
v["provider"] = that.Db.Get("provider", "id,name,level,discount,avatar,title,description,"+
|
||||
"click_num_base+click_num AS click_num,handle_num_base+handle_num AS handle_num,favorite_num_base+favorite_num AS favorite_num,modify_time", Map{"id": v.GetCeilInt64("provider_id")})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"github.com/tealeg/xlsx"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DataToExcel(w http.ResponseWriter, r *http.Request, titleList []string, dataList []common.Map, fileName string) {
|
||||
// 生成一个新的文件
|
||||
file := xlsx.NewFile()
|
||||
// 添加sheet页
|
||||
sheet, _ := file.AddSheet("Sheet1")
|
||||
// 插入表头
|
||||
titleRow := sheet.AddRow()
|
||||
for _, v := range titleList {
|
||||
cell := titleRow.AddCell()
|
||||
cell.Value = v
|
||||
cell.GetStyle().Font.Color = "00FF0000"
|
||||
}
|
||||
for _, v1 := range dataList {
|
||||
row := sheet.AddRow()
|
||||
// 插入内容
|
||||
for _, v := range titleList {
|
||||
cell := row.AddCell()
|
||||
cell.SetValue(v1.Get(v))
|
||||
}
|
||||
|
||||
}
|
||||
//file.Save("D:\\temp\\vip_excel"+fileName)
|
||||
|
||||
fileName = fmt.Sprintf("%s.xlsx", fileName)
|
||||
//_ = file.Save(fileName)
|
||||
w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
|
||||
w.Header().Add("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
|
||||
var buffer bytes.Buffer
|
||||
_ = file.Write(&buffer)
|
||||
content := bytes.NewReader(buffer.Bytes())
|
||||
http.ServeContent(w, r, fileName, time.Now(), content)
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Project 管理端项目
|
||||
var Project = Proj{
|
||||
"article": ArticleCtr,
|
||||
"company": CompanyCtr,
|
||||
"declare": DeclareCtr,
|
||||
"favorite": FavoriteCtr,
|
||||
"lxcx": Lxcx,
|
||||
"matters": MattersCtr,
|
||||
"notify": NotifyCtr,
|
||||
"order": OrderCtr,
|
||||
"policy": PolicyCtr,
|
||||
"provider": ProviderCtr,
|
||||
"search_record": SearchRecordCtr,
|
||||
"sms": Sms,
|
||||
"tag": TagCtr,
|
||||
"upan": UpanCtr,
|
||||
"user": UserCtr,
|
||||
"vip_order": VipOrderCtr,
|
||||
"websocket": WebsocketCtr,
|
||||
"wechath5": Wechath5,
|
||||
"wechatmini": Wechath5,
|
||||
"coupon": CouponCtr,
|
||||
}
|
||||
|
||||
//生成随机码的6位md5
|
||||
func getSn() string {
|
||||
x := Rand(8)
|
||||
return Substr(Md5(ObjToStr(int64(x)+time.Now().UnixNano()+int64(Rand(6)))), 0, 6)
|
||||
}
|
||||
|
||||
//生成随机码的4位随机数
|
||||
func getCode() string {
|
||||
//res := ""
|
||||
//for i := 0; i < 4; i++ {
|
||||
res := ObjToStr(RandX(1000, 9999))
|
||||
//}
|
||||
return res
|
||||
}
|
||||
|
||||
func strToArray(str string) Slice {
|
||||
s := Slice{}
|
||||
|
||||
olds := strings.Split(str, ",")
|
||||
for _, v := range olds {
|
||||
if v != "" {
|
||||
s = append(s, v)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func arrayToStr(ars Slice) string {
|
||||
s := ","
|
||||
if ars == nil {
|
||||
return s
|
||||
}
|
||||
for k, _ := range ars {
|
||||
s = s + ars.GetString(k) + ","
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//认证公共方案
|
||||
func auth(that *Context, phone, companyName, userName string) error {
|
||||
|
||||
user := that.Db.Get("user", "id,phone,salesman_id,company_id,provider_id,name", Map{"id": that.Session("user_id").ToCeilInt()})
|
||||
if user == nil {
|
||||
return errors.New("找不到用户")
|
||||
}
|
||||
company := that.Db.Get("company", "name,id", Map{"id": user.GetCeilInt("company_id")})
|
||||
|
||||
if company == nil {
|
||||
return errors.New("找不到企业")
|
||||
}
|
||||
//手机号与原来的不同则进行绑定
|
||||
if phone != user.GetString("phone") || companyName != company.GetString("name") || user.GetString("name") != userName {
|
||||
//微信验证成功
|
||||
if that.Session("wechat_phone").ToStr() == phone {
|
||||
that.Db.Update("user", Map{"phone": phone}, Map{"id": user.GetCeilInt("id")})
|
||||
if user.GetCeilInt("company_id") != 0 {
|
||||
that.Db.Update("company", Map{"phone": phone}, Map{"id": user.GetCeilInt("company_id")})
|
||||
}
|
||||
user["phone"] = phone
|
||||
} else if that.Req.FormValue("code") == "" || that.Session("phone").ToStr() != phone || that.Session("code").ToStr() != that.Req.FormValue("code") {
|
||||
|
||||
return errors.New("验证码错误")
|
||||
} else {
|
||||
that.Db.Update("user", Map{"phone": phone, "name": userName}, Map{"id": user.GetCeilInt("id")})
|
||||
if user.GetCeilInt("company_id") != 0 {
|
||||
that.Db.Update("company", Map{"phone": phone}, Map{"id": user.GetCeilInt("company_id")})
|
||||
}
|
||||
user["phone"] = phone
|
||||
}
|
||||
|
||||
}
|
||||
//
|
||||
//company := that.Db.Get("company", "name,id", Map{"id": user.GetCeilInt("company_id")})
|
||||
if company != nil {
|
||||
that.Db.Update("company", Map{"name": companyName}, Map{"id": company.GetCeilInt64("id")})
|
||||
if user.GetCeilInt64("salesman_id") != 0 {
|
||||
that.Db.Update("user", Map{"certification_flag": 1}, Map{"id": user.GetCeilInt("id")})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
company = Map{"name": companyName,
|
||||
"user_id": user.GetCeilInt("id"),
|
||||
}
|
||||
if user.GetCeilInt("salesman_id") != 0 {
|
||||
company["salesman_id"] = user.GetCeilInt("salesman_id")
|
||||
}
|
||||
if user.GetCeilInt("provider_id") != 0 {
|
||||
company["provider_id"] = user.GetCeilInt("provider_id")
|
||||
}
|
||||
if user.GetString("phone") != "" {
|
||||
company["phone"] = user.GetString("phone")
|
||||
}
|
||||
company["id"] = that.Db.Insert("company", company)
|
||||
if company.GetCeilInt64("id") == 0 {
|
||||
return errors.New("新建企业失败")
|
||||
}
|
||||
upUser := Map{"company_id": company.GetCeilInt64("id")}
|
||||
if user.GetCeilInt64("salesman_id") != 0 {
|
||||
upUser["certification_flag"] = 1
|
||||
|
||||
}
|
||||
that.Db.Update("user", upUser, Map{"id": that.Session("user_id").Data})
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// FilterEmoji 过滤 emoji 表情
|
||||
func FilterEmoji(content string) string {
|
||||
new_content := ""
|
||||
for _, value := range content {
|
||||
_, size := utf8.DecodeRuneInString(string(value))
|
||||
if size <= 3 {
|
||||
new_content += string(value)
|
||||
}
|
||||
}
|
||||
return new_content
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var Lxcx = Ctr{
|
||||
"lists": func(that *Context) {
|
||||
urlStr := "https://www.ruichuangshe.com/Resources/Services/sAppInterface.ashx?type=policyprojectbycompanyname&page=1&pagesize=10&code=8eb68adf79d660e57ccd69ea4bf340162e29e43ec344df62b364bd65b886da7fca9fb995ad93a5b485d66fd268f946f34cf0&CompanyName="
|
||||
companyName := that.Req.FormValue("company")
|
||||
companyName = url.QueryEscape(url.QueryEscape(companyName))
|
||||
|
||||
resp, err := http.Get(urlStr + companyName)
|
||||
if err != nil {
|
||||
log.Println("err")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println("err")
|
||||
}
|
||||
that.Display(0, common.ObjToMap(string(b)))
|
||||
},
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var MattersCtr = Ctr{
|
||||
"create": func(that *Context) {
|
||||
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
sn := that.Req.FormValue("sn")
|
||||
if sn == "" {
|
||||
that.Display(3, "没有SN码")
|
||||
return
|
||||
}
|
||||
serviceContent := that.Req.FormValue("service_content")
|
||||
if serviceContent == "" {
|
||||
that.Display(3, "没有服务内容")
|
||||
return
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "id,provider_id,name,nickname", Map{"AND": Map{"sn": sn, "del_flag": 0}})
|
||||
if salesman == nil {
|
||||
that.Display(4, "找不到服务商")
|
||||
return
|
||||
}
|
||||
|
||||
re := that.Db.Insert("matters", Map{
|
||||
"user_id": that.Session("user_id").Data,
|
||||
"salesman_id": salesman.GetCeilInt64("id"),
|
||||
"provider_id": salesman.GetCeilInt64("provider_id"),
|
||||
"type": 1,
|
||||
"service_content": serviceContent,
|
||||
"complete_flag": 0,
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
res := that.Db.Get("matters", "*", Map{"id": re})
|
||||
if res == nil {
|
||||
that.Display(4, "找不到事项")
|
||||
return
|
||||
}
|
||||
|
||||
res["salesman"] = that.Db.Get("salesman", "name,id", Map{"id": res.GetCeilInt64("salesman_id")})
|
||||
|
||||
res["provider"] = that.Db.Get("provider", "name,id", Map{"id": res.GetCeilInt64("provider_id")})
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("matters", "*", Map{"id": id})
|
||||
if res == nil {
|
||||
that.Display(4, "找不到事项")
|
||||
return
|
||||
}
|
||||
|
||||
res["salesman"] = that.Db.Get("salesman", "name,id", Map{"id": res.GetCeilInt64("salesman_id")})
|
||||
|
||||
res["provider"] = that.Db.Get("provider", "name,id", Map{"id": res.GetCeilInt64("provider_id")})
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
"edit": func(that *Context) {
|
||||
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
serviceEvaluation := ObjToInt(that.Req.FormValue("service_evaluation"))
|
||||
evaluationRemark := that.Req.FormValue("evaluation_remark")
|
||||
|
||||
if serviceEvaluation == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
matters := that.Db.Get("matters", "*", Map{"AND": Map{"id": id, "user_id": that.Session("user_id").Data}})
|
||||
if matters == nil {
|
||||
that.Display(4, "不是属于你的评价")
|
||||
return
|
||||
}
|
||||
|
||||
if matters.GetCeilInt("complete_flag") == 1 {
|
||||
that.Display(4, "已完成评价,不可重复评价")
|
||||
return
|
||||
}
|
||||
|
||||
that.Db.Update("matters", Map{"service_evaluation": serviceEvaluation, "evaluation_remark": evaluationRemark, "complete_flag": 1, "modify_time[#]": "now()"}, Map{"id": id})
|
||||
|
||||
that.Display(0, "评价成功")
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
tp := that.Req.FormValue("type")
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "user_id": that.Session("user_id").Data}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "content[~]": keywords}
|
||||
}
|
||||
|
||||
if tp != "" {
|
||||
data["type"] = ObjToInt(tp)
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modify_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modify_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "modify_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("matters", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("matters", "*", data)
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var NotifyCtr = Ctr{
|
||||
|
||||
"info": func(that *Context) {
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("notify", "*", Map{"id": id})
|
||||
|
||||
if res == nil {
|
||||
that.Display(4, "找不到通知公告")
|
||||
return
|
||||
}
|
||||
res["click_num"] = res.GetCeilInt64("click_num") + res.GetCeilInt64("click_num_base") + 1
|
||||
delete(res, "click_num_base")
|
||||
|
||||
res["favorite_num"] = res.GetCeilInt64("favorite_num") + res.GetCeilInt64("favorite_num_base")
|
||||
delete(res, "favorite_num_base")
|
||||
|
||||
article := that.Db.Get("article", "*", Map{"id": res.GetCeilInt64("article_id")})
|
||||
if article != nil {
|
||||
article["click_num"] = article.GetCeilInt64("click_num") + article.GetCeilInt64("click_num_base") + 1
|
||||
delete(article, "click_num_base")
|
||||
|
||||
article["favorite_num"] = article.GetCeilInt64("favorite_num") + article.GetCeilInt64("favorite_num_base")
|
||||
delete(article, "favorite_num_base")
|
||||
}
|
||||
|
||||
res["article"] = article
|
||||
//浏览量加1
|
||||
that.Db.Update("notify", Map{"click_num[#]": "click_num+1"}, Map{"id": id})
|
||||
//浏览量加1
|
||||
that.Db.Update("article", Map{"click_num[#]": "click_num+1"}, Map{"id": res.GetCeilInt64("article_id")})
|
||||
|
||||
//查询是否已关注
|
||||
if that.Session("user_id").Data != nil {
|
||||
favorite := that.Db.Get("favorite", "user_id,notify_id", Map{"AND": Map{"notify_id": id, "user_id": that.Session("user_id").ToCeilInt(), "del_flag": 0}})
|
||||
res["favorite"] = favorite
|
||||
}
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "notify_id[!]": nil}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "content[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["release_date[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["release_date[<=]"] = finishTime
|
||||
}
|
||||
|
||||
dispatchName := that.Req.FormValue("dispatch_name")
|
||||
if dispatchName != "" {
|
||||
data["dispatch_name"] = dispatchName
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "release_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("article", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("article", "id,title,description,department_id,click_num+click_num_base AS click_num,"+
|
||||
"favorite_num_base+favorite_num AS favorite_num,dispatch_num,dispatch_name,prepare_date,release_time,expire_date,area_id,status,policy_id,declare_id,notify_id,dispatch_name,policy_level", data)
|
||||
for _, v := range res {
|
||||
|
||||
if v.GetCeilInt("notify_id") > 0 {
|
||||
v["notify"] = that.Db.Get("notify", "id,tag", Map{"id": v.GetCeilInt("notify_id")})
|
||||
}
|
||||
//if v.GetCeilInt("policy_id")>0{
|
||||
// v["policy"]=that.Db.Get("policy","tag",Map{"id":v.GetCeilInt("policy_id")})
|
||||
//}
|
||||
//if v.GetCeilInt("declare_id")>0{
|
||||
// v["declare"]=that.Db.Get("declare","money_scope_min,money_scope_max,tag,status",Map{"id":v.GetCeilInt("declare_id")})
|
||||
//}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
var OrderCtr = Ctr{
|
||||
//创建订单
|
||||
"create": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
providerId := ObjToInt(that.Req.FormValue("provider_id"))
|
||||
phone := that.Req.FormValue("phone")
|
||||
companyName := that.Req.FormValue("company_name")
|
||||
userName := that.Req.FormValue("user_name")
|
||||
if providerId == 0 || len(phone) != 11 || len(companyName) < 4 || len(userName) < 2 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
err := auth(that, phone, companyName, userName)
|
||||
if err != nil {
|
||||
that.Display(3, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
declareId := ObjToInt(that.Req.FormValue("declare_id"))
|
||||
|
||||
tp := that.Req.FormValue("type")
|
||||
|
||||
//新建流程
|
||||
user := that.Db.Get("user", "*", Map{"id": that.Session("user_id").Data})
|
||||
|
||||
if user == nil {
|
||||
that.Display(4, "找不到用户")
|
||||
return
|
||||
}
|
||||
company := that.Db.Get("company", "name", Map{"id": user.GetCeilInt("company_id")})
|
||||
if company == nil {
|
||||
that.Display(4, "找不到企业")
|
||||
return
|
||||
}
|
||||
|
||||
provider := that.Db.Get("provider", "*", Map{"id": providerId})
|
||||
|
||||
if provider == nil {
|
||||
that.Display(4, "找不到该服务商")
|
||||
return
|
||||
}
|
||||
|
||||
//是否以前已经创建了该服务商的订单,如果创建了则直接跳转到订单详情中去
|
||||
oldOrder := that.Db.Get("order", "id", Map{"AND": Map{"provider_id": providerId, "user_id": that.Session("user_id").Data, "del_flag": 0, "status": 0}})
|
||||
if oldOrder != nil {
|
||||
orderRecord := Map{
|
||||
"order_id": oldOrder.GetCeilInt64("id"),
|
||||
"user_id": user.GetCeilInt64("id"),
|
||||
"remarks": user.GetString("nickname") + "向“" + provider.GetString("name") + "”服务商再次发起了订单请求",
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
}
|
||||
|
||||
orderRecord["id"] = that.Db.Insert("order_record", orderRecord)
|
||||
that.Db.Update("order", Map{"order_record_id": orderRecord.GetCeilInt64("id"), "modify_time[#]": "now()"}, Map{"id": oldOrder.GetCeilInt64("id")})
|
||||
|
||||
that.Display(0, oldOrder.GetCeilInt64("id"))
|
||||
return
|
||||
}
|
||||
|
||||
data := Map{
|
||||
"name": "购买“" + provider.GetString("title") + "”服务",
|
||||
"sn": "SN" + time.Now().Format("20060102150405") + getSn(),
|
||||
"user_id": user.GetCeilInt64("id"),
|
||||
"salesman_id": provider.GetCeilInt64("salesman_id"),
|
||||
"provider_id": provider.GetCeilInt64("id"),
|
||||
"company_id": company.GetCeilInt64("id"),
|
||||
"company_name": company.GetString("name"),
|
||||
"phone": user.GetString("phone"),
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
}
|
||||
|
||||
if declareId != 0 {
|
||||
data["policy_declare_flag"] = 1
|
||||
data["declare_id"] = declareId
|
||||
}
|
||||
|
||||
if tp == "policy_declare_flag" {
|
||||
data["policy_declare_flag"] = 1
|
||||
}
|
||||
|
||||
data["id"] = that.Db.Insert("order", data)
|
||||
if data.GetCeilInt64("id") == 0 {
|
||||
that.Display(4, "无法生成订单!")
|
||||
return
|
||||
}
|
||||
|
||||
orderRecord := Map{
|
||||
"order_id": data["id"],
|
||||
"user_id": user.GetCeilInt64("id"),
|
||||
"remarks": user.GetString("nickname") + "向“" + provider.GetString("name") + "”服务商发起了订单请求",
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
}
|
||||
|
||||
orderRecord["id"] = that.Db.Insert("order_record", orderRecord)
|
||||
|
||||
if orderRecord.GetCeilInt64("id") == 0 {
|
||||
that.Display(4, "无法生成订单记录!")
|
||||
return
|
||||
}
|
||||
|
||||
that.Db.Update("order", Map{"order_record_id": orderRecord.GetCeilInt64("id")}, Map{"id": data.GetCeilInt64("id")})
|
||||
|
||||
that.Display(0, data.GetCeilInt64("id"))
|
||||
},
|
||||
|
||||
"info": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("order", "*", Map{"AND": Map{"id": id, "user_id": that.Session("user_id").Data}})
|
||||
|
||||
if res == nil {
|
||||
that.Display(4, "找不到对应订单")
|
||||
return
|
||||
}
|
||||
if res.GetCeilInt("provider_id") > 0 {
|
||||
res["provider"] = that.Db.Get("provider", "name,title,phone", Map{"id": res.GetCeilInt("provider_id")})
|
||||
}
|
||||
|
||||
res["order_record"] = that.Db.Select("order_record", "remarks,modify_time", Map{"order_id": res.GetCeilInt("id"), "ORDER": "modify_time DESC"})
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "user_id": that.Session("user_id").Data}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"sn[~]": keywords, "company_name[~]": keywords, "name[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modify_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modify_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "modify_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("order", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("order", "*", data)
|
||||
for _, v := range res {
|
||||
|
||||
//if v.GetCeilInt("policy_id")>0{
|
||||
// v["policy"]=that.Db.Get("policy","id,tag",Map{"id":v.GetCeilInt("policy_id")})
|
||||
//}
|
||||
if v.GetCeilInt("provider_id") > 0 {
|
||||
v["provider"] = that.Db.Get("provider", "name,title", Map{"id": v.GetCeilInt("provider_id")})
|
||||
}
|
||||
if v.GetCeilInt("order_record_id") > 0 {
|
||||
v["order_record"] = that.Db.Get("order_record", "remarks,modify_time", Map{"id": v.GetCeilInt("order_record_id")})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var PolicyCtr = Ctr{
|
||||
|
||||
"info": func(that *Context) {
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("policy", "*", Map{"id": id})
|
||||
|
||||
if res == nil {
|
||||
that.Display(4, "找不到通知公告")
|
||||
return
|
||||
}
|
||||
res["click_num"] = res.GetCeilInt64("click_num") + res.GetCeilInt64("click_num_base") + 1
|
||||
delete(res, "click_num_base")
|
||||
|
||||
res["favorite_num"] = res.GetCeilInt64("favorite_num") + res.GetCeilInt64("favorite_num_base")
|
||||
delete(res, "favorite_num_base")
|
||||
|
||||
article := that.Db.Get("article", "*", Map{"id": res.GetCeilInt64("article_id")})
|
||||
if article != nil {
|
||||
article["click_num"] = article.GetCeilInt64("click_num") + article.GetCeilInt64("click_num_base") + 1
|
||||
delete(article, "click_num_base")
|
||||
|
||||
article["favorite_num"] = article.GetCeilInt64("favorite_num") + article.GetCeilInt64("favorite_num_base")
|
||||
delete(article, "favorite_num_base")
|
||||
}
|
||||
|
||||
res["article"] = article
|
||||
|
||||
//浏览量加1
|
||||
that.Db.Update("policy", Map{"click_num[#]": "click_num+1"}, Map{"id": id})
|
||||
//浏览量加1
|
||||
that.Db.Update("article", Map{"click_num[#]": "click_num+1"}, Map{"id": res.GetCeilInt64("article_id")})
|
||||
|
||||
//查询是否已关注
|
||||
if that.Session("user_id").Data != nil {
|
||||
favorite := that.Db.Get("favorite", "user_id,policy_id", Map{"AND": Map{"policy_id": id, "user_id": that.Session("user_id").ToCeilInt(), "del_flag": 0}})
|
||||
res["favorite"] = favorite
|
||||
}
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "policy_id[!]": nil}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "content[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["release_date[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["release_date[<=]"] = finishTime
|
||||
}
|
||||
|
||||
dispatchName := that.Req.FormValue("dispatch_name")
|
||||
if dispatchName != "" {
|
||||
data["dispatch_name"] = dispatchName
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "release_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("article", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("article", "id,title,description,department_id,click_num+click_num_base AS click_num,"+
|
||||
"favorite_num_base+favorite_num AS favorite_num,dispatch_num,dispatch_name,prepare_date,release_time,expire_date,area_id,status,policy_id,declare_id,policy_id,dispatch_name,policy_level", data)
|
||||
for _, v := range res {
|
||||
|
||||
//if v.GetCeilInt("policy_id")>0{
|
||||
// v["policy"]=that.Db.Get("policy","id,tag",Map{"id":v.GetCeilInt("policy_id")})
|
||||
//}
|
||||
if v.GetCeilInt("policy_id") > 0 {
|
||||
v["policy"] = that.Db.Get("policy", "tag", Map{"id": v.GetCeilInt("policy_id")})
|
||||
}
|
||||
//if v.GetCeilInt("declare_id")>0{
|
||||
// v["declare"]=that.Db.Get("declare","money_scope_min,money_scope_max,tag,status",Map{"id":v.GetCeilInt("declare_id")})
|
||||
//}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var ProviderCtr = Ctr{
|
||||
//用户微信公众号或者小程序登录
|
||||
"info": func(that *Context) {
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("provider", "*", Map{"id": id})
|
||||
|
||||
if res == nil {
|
||||
that.Display(4, "找不到服务商")
|
||||
return
|
||||
}
|
||||
|
||||
//浏览量加1
|
||||
that.Db.Update("provider", Map{"click_num[#]": "click_num+1"}, Map{"id": id})
|
||||
|
||||
//查询是否已关注
|
||||
if that.Session("user_id").Data != nil {
|
||||
favorite := that.Db.Get("favorite", "user_id,provider_id", Map{"AND": Map{"provider_id": id, "user_id": that.Session("user_id").ToCeilInt(), "del_flag": 0}})
|
||||
res["favorite"] = favorite
|
||||
}
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "state": 0}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"name[~]": keywords, "title[~]": keywords}
|
||||
}
|
||||
|
||||
tp := that.Req.FormValue("type")
|
||||
if tp != "" {
|
||||
data[tp] = 1
|
||||
}
|
||||
|
||||
policyDeclareFlag := that.Req.FormValue("policy_declare_flag")
|
||||
if policyDeclareFlag != "" {
|
||||
data["policy_declare_flag"] = ObjToInt(policyDeclareFlag)
|
||||
}
|
||||
|
||||
intellectualPropertyFlag := that.Req.FormValue("intellectual_property_flag")
|
||||
if intellectualPropertyFlag != "" {
|
||||
data["intellectual_property_flag"] = ObjToInt(intellectualPropertyFlag)
|
||||
}
|
||||
|
||||
taxOnsultingFlag := that.Req.FormValue("tax_onsulting_flag")
|
||||
if taxOnsultingFlag != "" {
|
||||
data["tax_onsulting_flag"] = ObjToInt(taxOnsultingFlag)
|
||||
}
|
||||
|
||||
lawFlag := that.Req.FormValue("law_flag")
|
||||
if lawFlag != "" {
|
||||
data["law_flag"] = ObjToInt(lawFlag)
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "id DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("provider", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("provider", "id,name,level,discount,avatar,title,description,"+
|
||||
"click_num_base+click_num AS click_num,handle_num_base+handle_num AS handle_num,favorite_num_base+favorite_num AS favorite_num,modify_time,policy_declare_flag,intellectual_property_flag,tax_onsulting_flag,law_flag", data)
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var SearchRecordCtr = Ctr{
|
||||
|
||||
"search": func(that *Context) {
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
//if pageSize <= 0 {
|
||||
pageSize = 50
|
||||
//}
|
||||
|
||||
data := Map{"del_flag": 0}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["search_company_name[~]"] = keywords
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["create_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["create_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data}
|
||||
} else {
|
||||
data["ORDER"] = "create_time DESC"
|
||||
}
|
||||
|
||||
count := that.Db.Count("search_record", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("search_record", "id,search_company_name,policy_match_count,money_scope,create_time", data)
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
|
||||
},
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
"code.hoteas.com/golang/hotime/dri/ddsms"
|
||||
)
|
||||
|
||||
var Sms = Ctr{
|
||||
//只允许微信验证过的或者登录成功的发送短信
|
||||
"send": func(that *Context) {
|
||||
|
||||
if that.Session("wechat_id").Data == nil && that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录不可发送短信")
|
||||
return
|
||||
}
|
||||
|
||||
phone := that.Req.FormValue("phone")
|
||||
if len(phone) < 11 {
|
||||
that.Display(3, "手机号格式错误")
|
||||
return
|
||||
}
|
||||
code := getCode()
|
||||
that.Session("phone", phone)
|
||||
that.Session("code", code)
|
||||
|
||||
ddsms.DDY.SendYZM(phone, that.Config.GetString("smsLogin"), map[string]string{"code": code})
|
||||
|
||||
that.Display(0, "发送成功")
|
||||
},
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var TagCtr = Ctr{
|
||||
|
||||
"create": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
|
||||
oldTag := that.Db.Get("tag", "id", Map{"name": name})
|
||||
if oldTag != nil {
|
||||
that.Display(4, "此标签已存在")
|
||||
return
|
||||
}
|
||||
|
||||
re := that.Db.Insert("tag", Map{
|
||||
"user_id": that.Session("user_id").Data,
|
||||
"name": name,
|
||||
"remark": "用户上传",
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"state": 1, //先置为异常状态,等待审核通过
|
||||
"del_flag": 0,
|
||||
})
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "添加失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, "添加成功")
|
||||
return
|
||||
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 40
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "state": 0}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"name[~]": keywords, "remark[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modify_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modify_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data}
|
||||
}
|
||||
|
||||
count := that.Db.Count("tag", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("tag", "id,name,remark", data)
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var UpanCtr = Ctr{
|
||||
"login": func(that *Context) {
|
||||
timestamp := that.Req.FormValue("timestamp")
|
||||
sn := that.Req.FormValue("sn")
|
||||
|
||||
//str,_:=EnPwdCode([]byte(lus[len(lus)-1]+":"+ObjToStr(t)))//
|
||||
re, e := DePwdCode(sn)
|
||||
|
||||
if e != nil {
|
||||
that.Display(3, "数据异常")
|
||||
return
|
||||
}
|
||||
reStr := string(re)
|
||||
realSn := strings.Replace(reStr, ":"+timestamp, "", -1)
|
||||
if len(realSn)+len(timestamp)+1 != len(reStr) {
|
||||
that.Display(4, "数据验证失败")
|
||||
return
|
||||
}
|
||||
fmt.Println("U盘校验", realSn)
|
||||
user := that.Db.Get("user", "*", common.Map{"upankey": realSn})
|
||||
if user == nil {
|
||||
that.Display(5, "还没有绑定用户")
|
||||
return
|
||||
}
|
||||
|
||||
that.Session("user_id", user.GetCeilInt("id"))
|
||||
that.Display(0, "登录成功")
|
||||
},
|
||||
|
||||
"create": func(that *Context) {
|
||||
timestamp := that.Req.FormValue("timestamp")
|
||||
sn := that.Req.FormValue("sn")
|
||||
|
||||
//str,_:=EnPwdCode([]byte(lus[len(lus)-1]+":"+ObjToStr(t)))//
|
||||
re, e := DePwdCode(sn)
|
||||
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
that.Display(3, "数据异常")
|
||||
return
|
||||
}
|
||||
reStr := string(re)
|
||||
realSn := strings.Replace(reStr, ":"+timestamp, "", -1)
|
||||
if len(realSn)+len(timestamp)+1 != len(reStr) {
|
||||
that.Display(4, "数据验证失败")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("U盘校验", realSn)
|
||||
|
||||
uuser := that.Db.Get("user", "id", common.Map{"upankey": realSn})
|
||||
|
||||
if uuser != nil {
|
||||
that.Display(4, "已经绑定了其他企业")
|
||||
return
|
||||
}
|
||||
|
||||
phone := that.Req.FormValue("phone")
|
||||
companyName := that.Req.FormValue("company_name")
|
||||
userName := that.Req.FormValue("user_name")
|
||||
|
||||
if len(phone) != 11 || len(companyName) < 4 || len(userName) < 2 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
//验证不成功则反馈
|
||||
err := auth(that, phone, companyName, userName)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
that.Display(3, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
num := that.Db.Update("user", common.Map{"upankey": realSn}, common.Map{"id": that.Session("user_id").Data})
|
||||
if num == 0 {
|
||||
that.Display(4, "更新失败")
|
||||
return
|
||||
}
|
||||
that.Display(0, "绑定成功")
|
||||
|
||||
},
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var UserCtr = Ctr{
|
||||
"test": func(that *Context) {
|
||||
that.Session("user_id", 1)
|
||||
that.Session("wechat_id", 1)
|
||||
that.Display(0, 1)
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"info": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
user := that.Db.Get("user", "*", Map{"id": that.Session("user_id").ToCeilInt()})
|
||||
if user == nil {
|
||||
that.Display(2, "获取个人信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if user.GetCeilInt("company_id") == 0 {
|
||||
companyId := that.Db.Insert("company", Map{
|
||||
"user_id": user["id"],
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
user["company_id"] = companyId
|
||||
that.Db.Update("user", Map{"company_id": companyId}, Map{"id": user.GetCeilInt("id")})
|
||||
}
|
||||
|
||||
delete(user, "password")
|
||||
|
||||
company := that.Db.Get("company", "id,name,social_code,business_license", Map{"id": user.GetCeilInt("company_id")})
|
||||
salesman := that.Db.Get("salesman", "id,name", Map{"id": user.GetCeilInt("salesman_id")})
|
||||
provider := that.Db.Get("provider", "id,name", Map{"id": user.GetCeilInt("provider_id")})
|
||||
|
||||
if user != nil {
|
||||
user["company"] = company
|
||||
user["salesman"] = salesman
|
||||
user["provider"] = provider
|
||||
}
|
||||
|
||||
that.Display(0, user)
|
||||
},
|
||||
|
||||
"edit": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
nickname := that.Req.FormValue("nickname")
|
||||
sex := that.Req.FormValue("sex")
|
||||
email := that.Req.FormValue("email")
|
||||
avatar := that.Req.FormValue("avatar")
|
||||
address := that.Req.FormValue("address")
|
||||
phone := that.Req.FormValue("phone") //如果更换手机号则要有新的短信验证码
|
||||
code := that.Req.FormValue("code")
|
||||
|
||||
data := Map{"modify_time[#]": "now()"}
|
||||
if name != "" {
|
||||
data["name"] = name
|
||||
}
|
||||
if nickname != "" {
|
||||
data["nickname"] = nickname
|
||||
}
|
||||
if sex != "" {
|
||||
data["sex"] = sex
|
||||
}
|
||||
if email != "" {
|
||||
data["email"] = email
|
||||
}
|
||||
if avatar != "" {
|
||||
data["avatar"] = avatar
|
||||
}
|
||||
if address != "" {
|
||||
data["address"] = address
|
||||
}
|
||||
if phone != "" {
|
||||
|
||||
//微信验证成功
|
||||
if that.Session("wechat_phone").ToStr() == phone {
|
||||
data["phone"] = phone
|
||||
} else if code == "" || that.Session("phone").ToStr() != phone || that.Session("code").ToStr() != code {
|
||||
that.Display(3, "手机短信验证失败")
|
||||
return
|
||||
}
|
||||
data["phone"] = phone
|
||||
}
|
||||
|
||||
re := that.Db.Update("user", data, Map{"id": that.Session("user_id").Data})
|
||||
if re == 0 {
|
||||
that.Display(4, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, "修改成功")
|
||||
},
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/wechat"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var VipOrderCtr = Ctr{
|
||||
//创建V订单
|
||||
"create": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
phone := that.Req.FormValue("phone")
|
||||
companyName := that.Req.FormValue("company_name")
|
||||
userName := that.Req.FormValue("user_name")
|
||||
if len(phone) != 11 || len(companyName) < 4 || len(userName) < 2 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
that.Db.Delete("vip_order", Map{"AND": Map{"user_id": that.Session("user_id").Data, "status": 0}})
|
||||
|
||||
user := that.Db.Get("user", "*", Map{"id": that.Session("user_id").Data})
|
||||
if user == nil {
|
||||
that.Display(2, "找不到此用户")
|
||||
return
|
||||
}
|
||||
|
||||
wc := that.Db.Get("wechat", "openid", Map{"AND": Map{"appid": that.Config.GetString("wechatAppID"), "user_id": that.Session("user_id").Data}})
|
||||
if wc == nil {
|
||||
that.Display(2, "没有获取微信个人信息")
|
||||
return
|
||||
}
|
||||
|
||||
err := auth(that, phone, companyName, userName)
|
||||
|
||||
if err != nil {
|
||||
that.Display(3, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data := Map{
|
||||
"sn": "SN" + time.Now().Format("20060102150405") + getSn(),
|
||||
//"name":"1年VIP会员",
|
||||
"amount": 36000, //720元
|
||||
"user_id": user.GetCeilInt64("id"),
|
||||
"company_id": user.GetCeilInt("company_id"),
|
||||
"expiration_time": time.Now().Add(365 * 24 * time.Hour).Format("2006-01-02 15:04:05"),
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
"status": 0,
|
||||
}
|
||||
tp := "购买"
|
||||
if user.GetString("expiration_time") != "" {
|
||||
data["old_expiration_time"] = user.GetString("expiration_time")
|
||||
|
||||
t, e := time.Parse("2006-01-02 15:04:05", user.GetString("expiration_time"))
|
||||
fmt.Println(e, "时间创建失败")
|
||||
if t.Unix() >= time.Now().Unix() {
|
||||
tp = "续订"
|
||||
data["expiration_time"] = t.Add(365 * 24 * time.Hour).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if user.GetCeilInt("provider_id") != 0 {
|
||||
data["provider_id"] = user.GetCeilInt("provider_id")
|
||||
data["amount"] = 36000
|
||||
//tp=tp
|
||||
}
|
||||
//data["amount"] = 1
|
||||
|
||||
if user.GetCeilInt("salesman_id") != 0 {
|
||||
data["salesman_id"] = user.GetCeilInt("salesman_id")
|
||||
}
|
||||
data["name"] = companyName + tp + "1年VIP会员"
|
||||
|
||||
jsParams, e := wechat.WxPay.GetJsOrder(data.GetCeilInt64("amount"), that.Config.GetString("wechatAppID"), wc.GetString("openid"), data.GetString("name"), data.GetString("sn"), that.Config.GetString("wechatAppNotifyUrl"))
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
that.Display(4, e)
|
||||
return
|
||||
}
|
||||
re := that.Db.Insert("vip_order", data)
|
||||
fmt.Println(re)
|
||||
that.Display(0, jsParams)
|
||||
},
|
||||
"callback": func(that *Context) {
|
||||
data, e := wechat.WxPay.CallbackJsOrder(that.Req)
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
//that.Display(4,e)
|
||||
|
||||
fmt.Println("返回数据错误", e)
|
||||
return
|
||||
}
|
||||
|
||||
sn := data.OutTradeNo
|
||||
amount := int64(data.Amount.Total)
|
||||
state := data.TradeState
|
||||
//state:="SUCCESS"
|
||||
//data := Map{"ces": "das"}
|
||||
//sn := that.Req.FormValue("sn")
|
||||
//amount := ObjToCeilInt64(that.Req.FormValue("amount"))
|
||||
|
||||
if state != "SUCCESS" {
|
||||
|
||||
fmt.Println("购买返回失败", data)
|
||||
return
|
||||
}
|
||||
|
||||
vipOrder := that.Db.Get("vip_order", "*", Map{"sn": sn})
|
||||
|
||||
if vipOrder == nil {
|
||||
|
||||
fmt.Println("找不到订单", vipOrder, data)
|
||||
return
|
||||
}
|
||||
user := that.Db.Get("user", "*", Map{"id": vipOrder.GetCeilInt("user_id")})
|
||||
if user == nil {
|
||||
|
||||
fmt.Println("找不到用户", vipOrder, data)
|
||||
return
|
||||
}
|
||||
if vipOrder.GetCeilInt64("amount") != amount {
|
||||
|
||||
fmt.Println("金额不符", user, vipOrder, amount, data)
|
||||
return
|
||||
}
|
||||
|
||||
that.Db.Update("vip_order", Map{"status": 1}, Map{"id": vipOrder.GetCeilInt("id")})
|
||||
|
||||
idata := Map{"expiration_time": time.Now().Add(365 * 24 * time.Hour).Format("2006-01-02 15:04:05")}
|
||||
if user.GetString("expiration_time") != "" {
|
||||
|
||||
t, e := time.Parse("2006-01-02 15:04:05", user.GetString("expiration_time"))
|
||||
fmt.Println(e, "时间创建失败")
|
||||
if t.Unix() >= time.Now().Unix() {
|
||||
idata["expiration_time"] = t.Add(365 * 24 * time.Hour).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
}
|
||||
|
||||
re := that.Db.Update("user", idata, Map{"id": user.GetCeilInt("id")})
|
||||
if re == 0 {
|
||||
fmt.Println("购买失败", user, vipOrder, re, data)
|
||||
|
||||
return
|
||||
}
|
||||
fmt.Println("成功购买", user, vipOrder, re, data)
|
||||
|
||||
//购买成功后赠送10张券
|
||||
//user_id := that.Session("user_id").Data
|
||||
data2 := Map{
|
||||
"user_id": that.Session("user_id").Data,
|
||||
"coupon_id": 1,
|
||||
"code_no": "SN" + time.Now().Format("20060102150405") + getSn(),
|
||||
"effective_start_time[#]": "NOW()",
|
||||
"effective_end_time": time.Now().AddDate(0, 6, 0).Format("2006-01-02 15:04:05"),
|
||||
"source_type": 1,
|
||||
"status": 0,
|
||||
"admin_id": user.GetCeilInt("admin_id"),
|
||||
"create_time[#]": "NOW()",
|
||||
}
|
||||
for n := 0; n < 5; n++ {
|
||||
that.Db.Insert("coupon_user", data2)
|
||||
}
|
||||
data2["effective_end_time"] = time.Now().AddDate(1, 0, 0).Format("2006-01-02 15:04:05")
|
||||
for n := 0; n < 5; n++ {
|
||||
that.Db.Insert("coupon_user", data2)
|
||||
}
|
||||
return
|
||||
|
||||
},
|
||||
"export": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
buy_date := ObjToStr(that.Req.FormValue("buy_date"))
|
||||
if buy_date == "" {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
//data := that.Db.Query("SELECT vo.sn 订单号, vo.`name` 订单名, vo.user_id 购买用户ID,usr.`name` 购买用户名, usr.phone 购买用户电话, cp.id 企业ID, cp.`name` 企业名,\nvo.expiration_time 过期时间, vo.old_expiration_time 订购前到期时间, vo.amount `订单金额_单位(分)`,\nvo.salesman_id 业务员ID,sm.`name` 业务员名, sm.phone 业务员电话, pv.`name` 服务商名 \nFROM vip_order vo\nLEFT JOIN salesman sm ON sm.id = vo.salesman_id\nLEFT JOIN provider pv ON pv.id = vo.provider_id\nLEFT JOIN `user` usr ON usr.id = vo.user_id\nLEFT JOIN company cp ON cp.id = vo.company_id\nWHERE vo.`status` = 1 AND vo.create_time >= '" + buy_date + "' \nORDER BY vo.create_time")
|
||||
//data := that.Db.Query("SELECT vo.sn 订单号, vo.`name` 订单名, vo.user_id 购买用户ID,usr.`name` 购买用户名, usr.phone 购买用户电话, cp.id 企业ID, cp.`name` 企业名,\nvo.expiration_time 过期时间, vo.old_expiration_time 订购前到期时间, vo.amount `订单金额_单位(分)`,\nvo.salesman_id 业务员ID,sm.`name` 业务员名, sm.phone 业务员电话, pv.`name` 服务商名 \nFROM vip_order vo\n" +
|
||||
// "LEFT JOIN salesman sm ON sm.id = vo.salesman_id\nLEFT JOIN provider pv ON pv.id = vo.provider_id\nLEFT JOIN `user` usr" +
|
||||
// " ON usr.id = vo.user_id\nLEFT JOIN company cp ON cp.id = vo.company_id\nWHERE" +
|
||||
// " vo.`status` = 1 AND vo.create_time >= ? \nORDER BY vo.create_time",buy_date)
|
||||
|
||||
data := that.Db.Select("vip_order", Map{"[>]salesman": "salesman.id=vip_order.salesman_id",
|
||||
"[>]provider": "provider.id = vip_order.provider_id",
|
||||
"[>]user": "user.id = vip_order.user_id",
|
||||
"[>]company": "company.id = vip_order.company_id",
|
||||
}, "vip_order.sn 订单号, vip_order.`name` 订单名, vip_order.user_id 购买用户ID,user.`name` 购买用户名, user.phone 购买用户电话, company.id 企业ID, company.`name` 企业名,\nvip_order.expiration_time 过期时间, vip_order.amount `订单金额_单位(分)`,\nvip_order.salesman_id 业务员ID,salesman.`name` 业务员名, salesman.phone 业务员电话, provider.`name` 服务商名",
|
||||
Map{"AND": Map{"vip_order.status": 1, "vip_order.create_time[>=]": buy_date}, "ORDER": "vip_order.create_time DESC"})
|
||||
|
||||
if len(data) == 0 {
|
||||
that.Display(0, "今日没有vip购买信息数据")
|
||||
return
|
||||
}
|
||||
|
||||
var titleList []string
|
||||
if data != nil {
|
||||
m := data[0]
|
||||
for k, _ := range m {
|
||||
titleList = append(titleList, k)
|
||||
}
|
||||
}
|
||||
|
||||
var dataList []Map
|
||||
for _, v := range data {
|
||||
dataList = append(dataList, v)
|
||||
}
|
||||
|
||||
//打印最后一次sql语句
|
||||
//that.Db.LastQuery
|
||||
//请求的数据
|
||||
//that.Db.LastData
|
||||
//that.Db.LastErr
|
||||
//appIns.Db.Select("company",common.Map{"[<>]user":"company.id=user.company_id"},"company.id as id",common.Map{"AND":common.Map{"id[!]":nil},"ORDER":"id DESC"})
|
||||
//appIns.Db.Query("select * from user where id = ? and name = ?",common.Slice{1,"nn"})
|
||||
|
||||
//titleList:= []string{"aaaa", "vvvvv", "dddd", "eeeee", "gfgggg"}
|
||||
DataToExcel(that.Resp, that.Req, titleList, dataList, buy_date+"vip购买信息分析数据")
|
||||
},
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"golang.org/x/net/websocket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WSClient struct {
|
||||
ID string
|
||||
*websocket.Conn
|
||||
time.Time
|
||||
DeadTime time.Time
|
||||
IsDead bool
|
||||
}
|
||||
|
||||
//websocket链接池
|
||||
var WsUserMap = map[string][]*WSClient{}
|
||||
var WSMasterID = ""
|
||||
|
||||
func WsSendMsg(ws *WSClient, data Map) bool {
|
||||
if WsUserMap[ws.ID] == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range WsUserMap[ws.ID] {
|
||||
if v.IsDead || v == ws {
|
||||
//WsUserMap[ws.ID]=WsUserMap[ws.ID][:k]
|
||||
continue
|
||||
}
|
||||
|
||||
str := data.ToJsonString()
|
||||
v.Conn.Write([]byte(str))
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var WebsocketCtr = Ctr{
|
||||
"conn": func(this *Context) {
|
||||
id := this.SessionId
|
||||
if WsUserMap[id] == nil {
|
||||
WsUserMap[id] = []*WSClient{}
|
||||
}
|
||||
hdler := websocket.Handler(func(ws *websocket.Conn) {
|
||||
|
||||
client := &WSClient{ID: id, Conn: ws, Time: time.Now(), DeadTime: time.Now(), IsDead: false}
|
||||
|
||||
WsUserMap[id] = append(WsUserMap[id], client)
|
||||
var message string
|
||||
for true {
|
||||
err := websocket.Message.Receive(ws, &message)
|
||||
if err != nil {
|
||||
client.DeadTime = time.Now()
|
||||
client.IsDead = true
|
||||
return
|
||||
}
|
||||
|
||||
data := Map{}
|
||||
data.JsonToMap(message)
|
||||
WsSendMsg(client, data)
|
||||
//switch data.GetString("type") {
|
||||
//
|
||||
//}
|
||||
|
||||
}
|
||||
})
|
||||
hdler.ServeHTTP(this.Resp, this.Req)
|
||||
},
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/wechat"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Wechath5 = Ctr{
|
||||
|
||||
//微信注册,0已经完整的注册了,1还没有注册
|
||||
"code": func(that *Context) {
|
||||
|
||||
if that.Req.FormValue("code") == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
appid, resToken, userInfo, err := wechat.H5Program.GetUserInfo(that.Req.FormValue("code"))
|
||||
|
||||
if err != nil {
|
||||
that.Display(4, err)
|
||||
return
|
||||
}
|
||||
//此次获取的微信信息
|
||||
wechatInfo := Map{
|
||||
"openid": userInfo.OpenID,
|
||||
"acttoken": resToken.AccessToken,
|
||||
"retoken": resToken.RefreshToken,
|
||||
"appid": appid,
|
||||
"unionid": userInfo.Unionid,
|
||||
"nickname": FilterEmoji(userInfo.Nickname),
|
||||
"avatar": userInfo.HeadImgURL,
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
"type": 1,
|
||||
}
|
||||
|
||||
userId := 0
|
||||
|
||||
//这个defer放到上面,防止出现用户第一次登陆的时候出现不返回company和salesman的情况
|
||||
//如果有则直接返回用户信息到微信小程序里
|
||||
defer func() {
|
||||
if userId != 0 {
|
||||
user := that.Db.Get("user", "*", Map{"id": userId})
|
||||
if user == nil {
|
||||
that.Display(4, "获取个人信息失败")
|
||||
return
|
||||
}
|
||||
delete(user, "password")
|
||||
company := that.Db.Get("company", "id,name", Map{"id": user.GetCeilInt("company_id")})
|
||||
|
||||
//消除company没有创建的影响
|
||||
if company == nil {
|
||||
companyId := that.Db.Insert("company", Map{
|
||||
"user_id": user["id"],
|
||||
"provider_id": user["provider_id"],
|
||||
"salesman_id": user["salesman_id"],
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
user["company_id"] = companyId
|
||||
that.Db.Update("user", Map{"company_id": companyId}, Map{"id": user.GetCeilInt("id")})
|
||||
company = that.Db.Get("company", "*", Map{"id": user.GetCeilInt("company_id")})
|
||||
}
|
||||
salesman := that.Db.Get("salesman", "id,name", Map{"id": user.GetCeilInt("salesman_id")})
|
||||
provider := that.Db.Get("provider", "id,name", Map{"id": user.GetCeilInt("provider_id")})
|
||||
|
||||
user["company"] = company
|
||||
user["salesman"] = salesman
|
||||
user["provider"] = provider
|
||||
|
||||
that.Display(0, user)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
|
||||
//有sn就关联业务员
|
||||
parentId := ObjToInt(that.Req.FormValue("parent_id"))
|
||||
if parentId == 0 {
|
||||
return
|
||||
}
|
||||
if userId == 0 || userId == parentId {
|
||||
return
|
||||
}
|
||||
puser := that.Db.Get("user", "`index`,id", Map{"id": parentId})
|
||||
if puser == nil {
|
||||
return
|
||||
}
|
||||
user := that.Db.Get("user", "parent_id", Map{"id": userId})
|
||||
if user.GetCeilInt("parent_id") != 0 {
|
||||
return
|
||||
}
|
||||
index1 := puser.GetString("index")
|
||||
if index1 == "," {
|
||||
index1 = index1 + ObjToStr(parentId) + ","
|
||||
}
|
||||
|
||||
//2022/5/11 zpw 添加字段:用户关联parenid的时间 (join_parent_time)
|
||||
that.Db.Update("user", Map{"parent_id": parentId, "index": index1 + ObjToStr(userId) + ",", "join_parent_time[#]": "NOW()"}, Map{"id": userId})
|
||||
//在这里做判断 能否通过分享领券
|
||||
//如果通过邀请获得的券超过了三张则不再赠送
|
||||
//赠送一张券
|
||||
data := Map{
|
||||
"user_id": parentId,
|
||||
"coupon_id": 1,
|
||||
"code_no": "SN" + time.Now().Format("20060102150405") + getSn(),
|
||||
"effective_start_time[#]": "NOW()",
|
||||
"effective_end_time": time.Now().AddDate(0, 6, 0).Format("2006-01-02 15:04:05"),
|
||||
"source_type": 3,
|
||||
"status": 0,
|
||||
"admin_id": user.GetCeilInt("admin_id"),
|
||||
"create_time[#]": "NOW()",
|
||||
}
|
||||
//先判断是否领取过 source_type=3 是通过拉新赠送的券
|
||||
couponCount := that.Db.Count("coupon_user", Map{"AND": Map{"user_id": parentId, "source_type": 3}})
|
||||
if couponCount < 3 {
|
||||
that.Db.Insert("coupon_user", data)
|
||||
}
|
||||
|
||||
}()
|
||||
//最后验证服务商是否绑定
|
||||
defer func() {
|
||||
//有sn就关联业务员
|
||||
sn := that.Req.FormValue("sn")
|
||||
if sn == "" {
|
||||
return
|
||||
}
|
||||
if userId == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"sn": sn})
|
||||
if salesman.GetCeilInt("id") == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
user := that.Db.Get("user", "*", Map{"id": userId})
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
|
||||
//用户都有企服人员关联
|
||||
if user.GetCeilInt("salesman_id") != 0 {
|
||||
|
||||
return
|
||||
}
|
||||
//用户没有企服商id
|
||||
//2022/5/11 zpw 添加字段:用户关联企服商业务人员的时间 (join_salesman_time)
|
||||
that.Db.Update("user", Map{"salesman_id": salesman.GetCeilInt64("id"), "provider_id": salesman.GetCeilInt64("provider_id"), "join_salesman_time[#]": "NOW()"}, Map{"id": user.GetCeilInt64("id")})
|
||||
if user.GetCeilInt("company_id") == 0 {
|
||||
return
|
||||
}
|
||||
//绑定企业
|
||||
that.Db.Update("company", Map{"salesman_id": salesman.GetCeilInt64("id"), "provider_id": salesman.GetCeilInt64("provider_id")}, Map{"id": user.GetCeilInt64("company_id")})
|
||||
|
||||
}()
|
||||
|
||||
wechat := that.Db.Get("wechat", "*", Map{"AND": Map{"openid": userInfo.OpenID, "del_flag": 0}})
|
||||
|
||||
if wechat != nil {
|
||||
//有用户直接返回
|
||||
if wechat.GetCeilInt("user_id") != 0 {
|
||||
|
||||
that.Db.Update("user", Map{"login_time[#]": "now()"}, Map{"id": wechat.GetCeilInt("user_id")})
|
||||
that.Session("wechat_id", wechat.GetCeilInt("id"))
|
||||
that.Session("user_id", wechat.GetCeilInt("user_id"))
|
||||
|
||||
userId = wechat.GetCeilInt("user_id")
|
||||
//that.Display(0, "登录成功")
|
||||
return
|
||||
}
|
||||
//没有用户继续查询数据库看是否有其他unionid
|
||||
wechat1 := that.Db.Get("wechat", "*", Map{"AND": Map{"unionid": userInfo.Unionid, "openid[!]": userInfo.OpenID, "user_id[>]": 0, "del_flag": 0}})
|
||||
|
||||
//其他表有该数据,则更新当前表数据信息
|
||||
if wechat1 != nil {
|
||||
|
||||
wechatInfo["user_id"] = wechat1.GetCeilInt("user_id")
|
||||
that.Db.Update("wechat", wechatInfo, Map{"id": wechat.GetCeilInt("id")})
|
||||
|
||||
that.Db.Update("user", Map{"login_time[#]": "now()"}, Map{"id": wechat1.GetCeilInt("user_id")})
|
||||
that.Session("wechat_id", wechat.GetCeilInt("id"))
|
||||
that.Session("user_id", wechatInfo.GetCeilInt("user_id"))
|
||||
|
||||
userId = wechatInfo.GetCeilInt("user_id")
|
||||
//that.Display(0, "登录成功")
|
||||
return
|
||||
}
|
||||
|
||||
//其他表也没有当前信息,则生成user表,并更新当前用户,一般不会走到这里来
|
||||
user := Map{
|
||||
"nickname": wechatInfo.GetString("nickname"),
|
||||
"avatar": wechatInfo.GetString("avatar"),
|
||||
"index": ",",
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"login_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
}
|
||||
user["id"] = that.Db.Insert("user", user)
|
||||
//创建企业
|
||||
user["company_id"] = that.Db.Insert("company", Map{
|
||||
"user_id": user["id"],
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
that.Db.Update("user", Map{"company_id": user["company_id"]}, Map{"id": user["id"]})
|
||||
|
||||
wechatInfo["user_id"] = user.GetCeilInt("id")
|
||||
|
||||
that.Db.Update("wechat", wechatInfo, Map{"id": wechat.GetCeilInt("id")})
|
||||
|
||||
that.Session("wechat_id", wechat.GetCeilInt("id"))
|
||||
that.Session("user_id", wechatInfo.GetCeilInt("user_id"))
|
||||
|
||||
userId = wechatInfo.GetCeilInt("user_id")
|
||||
//that.Display(0, "登录成功")
|
||||
return
|
||||
}
|
||||
user := Map{
|
||||
"nickname": wechatInfo.GetString("nickname"),
|
||||
"avatar": wechatInfo.GetString("avatar"),
|
||||
"index": ",",
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"login_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
}
|
||||
user["id"] = that.Db.Insert("user", user)
|
||||
user["company_id"] = that.Db.Insert("company", Map{
|
||||
"user_id": user["id"],
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
|
||||
that.Db.Update("user", Map{"company_id": user["company_id"]}, Map{"id": user["id"]})
|
||||
|
||||
wechatInfo["user_id"] = user.GetCeilInt("id")
|
||||
|
||||
wechatInfo["id"] = that.Db.Insert("wechat", wechatInfo)
|
||||
|
||||
if wechatInfo.GetCeilInt("id") == 0 {
|
||||
that.Display(4, "创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Session("wechat_id", wechatInfo.GetCeilInt("id"))
|
||||
that.Session("user_id", wechatInfo.GetCeilInt("user_id"))
|
||||
|
||||
userId = wechatInfo.GetCeilInt("user_id")
|
||||
//that.Display(0, "登录成功")
|
||||
|
||||
},
|
||||
//网页签名
|
||||
"sign": func(that *Context) {
|
||||
|
||||
signUrl := that.Req.FormValue("url")
|
||||
if signUrl == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
cfg1, e := wechat.H5Program.GetSignUrl(signUrl)
|
||||
|
||||
if e != nil {
|
||||
that.Display(4, e)
|
||||
return
|
||||
}
|
||||
|
||||
sign := Map{
|
||||
"appId": cfg1.AppID,
|
||||
"timestamp": cfg1.Timestamp,
|
||||
"nonceStr": cfg1.NonceStr,
|
||||
"signature": cfg1.Signature,
|
||||
}
|
||||
|
||||
that.Display(0, sign)
|
||||
|
||||
},
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/wechat"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var WechatMini = Ctr{
|
||||
"getphone": func(that *Context) {
|
||||
|
||||
//sessionKey := that.Req.FormValue("sessionkey")
|
||||
|
||||
if that.Session("wechat_id").Data == nil {
|
||||
that.Display(2, "请先登录")
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData := that.Req.FormValue("encryptedData")
|
||||
iv := that.Req.FormValue("iv")
|
||||
|
||||
if encryptedData == "" || iv == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
wechatIns := that.Db.Get("wechat", "sessionkey", Map{"id": that.Session("wechat_id").ToCeilInt()})
|
||||
_, re, e := wechat.MiniProgram.GetPhoneNumber(wechatIns.GetString("sessionkey"), encryptedData, iv)
|
||||
|
||||
if e != nil {
|
||||
that.Display(4, e)
|
||||
return
|
||||
}
|
||||
//临时存储用于校验
|
||||
that.Session("wechat_phone", re.PhoneNumber)
|
||||
that.Display(0, re.PhoneNumber)
|
||||
|
||||
},
|
||||
//检查是否已经有用户登录了,如果有直接登录,如果没有则查询unionid,有则直接返回用户信息,没有则返回null
|
||||
"code": func(that *Context) {
|
||||
|
||||
code := that.Req.FormValue("code")
|
||||
if code == "" {
|
||||
that.Display(3, "缺少code")
|
||||
return
|
||||
}
|
||||
|
||||
appid, re, e := wechat.MiniProgram.GetBaseUserInfo(code)
|
||||
fmt.Println(re)
|
||||
if e != nil {
|
||||
that.Display(4, e)
|
||||
return
|
||||
}
|
||||
|
||||
wchat := Map{"openid": re.OpenID,
|
||||
"appid": appid,
|
||||
"sessionkey": re.SessionKey,
|
||||
"unionid": re.UnionID,
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
"type": 2,
|
||||
}
|
||||
|
||||
userId := 0
|
||||
|
||||
//如果有则直接返回用户信息到微信小程序里
|
||||
defer func() {
|
||||
if userId != 0 {
|
||||
user := that.Db.Get("user", "*", Map{"id": userId})
|
||||
company := that.Db.Get("company", "id,name", Map{"id": user.GetCeilInt("company_id")})
|
||||
salesman := that.Db.Get("salesman", "id,name", Map{"id": user.GetCeilInt("salesman_id")})
|
||||
provider := that.Db.Get("provider", "id,name", Map{"id": user.GetCeilInt("provider_id")})
|
||||
if user == nil {
|
||||
that.Display(4, "获取个人信息失败")
|
||||
return
|
||||
}
|
||||
delete(user, "password")
|
||||
user["company"] = company
|
||||
user["salesman"] = salesman
|
||||
user["provider"] = provider
|
||||
that.Display(0, user)
|
||||
}
|
||||
}()
|
||||
|
||||
wechat := that.Db.Get("wechat", "*", Map{"AND": Map{"openid": re.OpenID, "user_id[!]": nil, "del_flag": 0}})
|
||||
//有该用户,则登录成功
|
||||
if wechat != nil {
|
||||
|
||||
that.Session("wechat_id", wechat.GetCeilInt("id"))
|
||||
that.Session("user_id", wechat.GetCeilInt("user_id"))
|
||||
//更新用户信息
|
||||
that.Db.Update("wechat", wchat, Map{"id": wechat.GetCeilInt("id")})
|
||||
//that.Display(0, "登录成功")
|
||||
userId = wechat.GetCeilInt("user_id")
|
||||
return
|
||||
}
|
||||
|
||||
//如果其他人有相关信息,则直接取用
|
||||
wechat = that.Db.Get("wechat", "*", Map{"AND": Map{"unionid": re.UnionID, "user_id[!]": nil, "del_flag": 0}})
|
||||
if wechat != nil {
|
||||
wechat1 := that.Db.Get("wechat", "*", Map{"AND": Map{"openid": re.OpenID, "del_flag": 0}})
|
||||
//有该信息,但没有相关的用户信息,则直接绑定其他的信息上来
|
||||
if wechat1 != nil {
|
||||
|
||||
//更新用户信息
|
||||
that.Db.Update("wechat", wchat, Map{"id": wechat1.GetCeilInt("id")})
|
||||
|
||||
that.Session("wechat_id", wechat1.GetCeilInt("id"))
|
||||
that.Session("user_id", wechat.GetCeilInt("user_id"))
|
||||
//that.Display(0, "登录成功")
|
||||
userId = wechat.GetCeilInt("user_id")
|
||||
return
|
||||
}
|
||||
//没有相关用户信息,则新建wechat并完成登录
|
||||
wchat["user_id"] = wechat["user_id"]
|
||||
wchat["create_time[#]"] = "now()"
|
||||
|
||||
wchat["id"] = that.Db.Insert("wechat", wchat)
|
||||
if wchat.GetCeilInt("id") == 0 {
|
||||
|
||||
that.Display(5, "创建wechat失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Session("wechat_id", wchat.GetCeilInt("id"))
|
||||
that.Session("user_id", wchat.GetCeilInt("user_id"))
|
||||
|
||||
userId = wchat.GetCeilInt("user_id")
|
||||
//that.Display(0, "登录成功")
|
||||
return
|
||||
}
|
||||
|
||||
user := Map{
|
||||
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"login_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
}
|
||||
user["id"] = that.Db.Insert("user", user)
|
||||
|
||||
user["company_id"] = that.Db.Insert("company", Map{
|
||||
"user_id": user["id"],
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
|
||||
that.Db.Update("user", Map{"company_id": user["company_id"]}, Map{"id": user["id"]})
|
||||
|
||||
wchat["user_id"] = user.GetCeilInt("id")
|
||||
wchat["create_time[#]"] = "now()"
|
||||
|
||||
wchat["id"] = that.Db.Insert("wechat", wchat)
|
||||
if wchat.GetCeilInt("id") == 0 {
|
||||
that.Display(5, "登录失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Session("wechat_id", wchat.GetCeilInt("id"))
|
||||
that.Session("user_id", wchat.GetCeilInt("user_id"))
|
||||
|
||||
userId = wchat.GetCeilInt("user_id")
|
||||
//that.Display(0, nil)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 压测配置
|
||||
type BenchConfig struct {
|
||||
URL string
|
||||
Concurrency int
|
||||
Duration time.Duration
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// 压测结果
|
||||
type BenchResult struct {
|
||||
TotalRequests int64
|
||||
SuccessRequests int64
|
||||
FailedRequests int64
|
||||
TotalDuration time.Duration
|
||||
MinLatency int64 // 纳秒
|
||||
MaxLatency int64
|
||||
AvgLatency int64
|
||||
P50Latency int64 // 50分位
|
||||
P90Latency int64 // 90分位
|
||||
P99Latency int64 // 99分位
|
||||
QPS float64
|
||||
}
|
||||
|
||||
// 延迟收集器
|
||||
type LatencyCollector struct {
|
||||
latencies []int64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (lc *LatencyCollector) Add(latency int64) {
|
||||
lc.mu.Lock()
|
||||
lc.latencies = append(lc.latencies, latency)
|
||||
lc.mu.Unlock()
|
||||
}
|
||||
|
||||
func (lc *LatencyCollector) GetPercentile(p float64) int64 {
|
||||
lc.mu.Lock()
|
||||
defer lc.mu.Unlock()
|
||||
|
||||
if len(lc.latencies) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 简单排序取百分位
|
||||
n := len(lc.latencies)
|
||||
idx := int(float64(n) * p)
|
||||
if idx >= n {
|
||||
idx = n - 1
|
||||
}
|
||||
|
||||
// 部分排序找第idx个元素
|
||||
return quickSelect(lc.latencies, idx)
|
||||
}
|
||||
|
||||
func quickSelect(arr []int64, k int) int64 {
|
||||
if len(arr) == 1 {
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
pivot := arr[len(arr)/2]
|
||||
var left, right, equal []int64
|
||||
|
||||
for _, v := range arr {
|
||||
if v < pivot {
|
||||
left = append(left, v)
|
||||
} else if v > pivot {
|
||||
right = append(right, v)
|
||||
} else {
|
||||
equal = append(equal, v)
|
||||
}
|
||||
}
|
||||
|
||||
if k < len(left) {
|
||||
return quickSelect(left, k)
|
||||
} else if k < len(left)+len(equal) {
|
||||
return pivot
|
||||
}
|
||||
return quickSelect(right, k-len(left)-len(equal))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 最大化利用CPU
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
fmt.Println("==========================================")
|
||||
fmt.Println(" 🔥 HoTime 极限压力测试 🔥")
|
||||
fmt.Println("==========================================")
|
||||
fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
|
||||
fmt.Println()
|
||||
|
||||
// 极限测试配置
|
||||
configs := []BenchConfig{
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 500, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 1000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 2000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 5000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 10000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
}
|
||||
|
||||
// 检查服务
|
||||
fmt.Println("正在检查服务是否可用...")
|
||||
if !checkService(configs[0].URL) {
|
||||
fmt.Println("❌ 服务不可用,请先启动示例应用")
|
||||
return
|
||||
}
|
||||
fmt.Println("✅ 服务已就绪")
|
||||
fmt.Println()
|
||||
|
||||
// 预热
|
||||
fmt.Println("🔄 预热中 (5秒)...")
|
||||
warmup(configs[0].URL, 100, 5*time.Second)
|
||||
fmt.Println("✅ 预热完成")
|
||||
fmt.Println()
|
||||
|
||||
var maxQPS float64
|
||||
var maxConcurrency int
|
||||
|
||||
// 执行极限测试
|
||||
for i, config := range configs {
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
fmt.Printf("【极限测试 %d】并发数: %d, 持续时间: %v\n", i+1, config.Concurrency, config.Duration)
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
|
||||
result := runBenchmark(config)
|
||||
printResult(result)
|
||||
|
||||
if result.QPS > maxQPS {
|
||||
maxQPS = result.QPS
|
||||
maxConcurrency = config.Concurrency
|
||||
}
|
||||
|
||||
// 检查是否达到瓶颈
|
||||
successRate := float64(result.SuccessRequests) / float64(result.TotalRequests) * 100
|
||||
if successRate < 95 {
|
||||
fmt.Println("\n⚠️ 成功率低于95%,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
|
||||
if result.AvgLatency > int64(100*time.Millisecond) {
|
||||
fmt.Println("\n⚠️ 平均延迟超过100ms,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// 测试间隔
|
||||
if i < len(configs)-1 {
|
||||
fmt.Println("冷却 5 秒...")
|
||||
time.Sleep(5 * time.Second)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// 最终报告
|
||||
fmt.Println()
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Println(" 📊 极限测试总结")
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Printf("最高 QPS: %.2f 请求/秒\n", maxQPS)
|
||||
fmt.Printf("最佳并发数: %d\n", maxConcurrency)
|
||||
fmt.Println()
|
||||
|
||||
// 并发用户估算
|
||||
estimateUsers(maxQPS)
|
||||
}
|
||||
|
||||
func checkService(url string) bool {
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return resp.StatusCode == 200
|
||||
}
|
||||
|
||||
func warmup(url string, concurrency int, duration time.Duration) {
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: concurrency * 2,
|
||||
MaxIdleConnsPerHost: concurrency * 2,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func runBenchmark(config BenchConfig) BenchResult {
|
||||
var (
|
||||
totalRequests int64
|
||||
successRequests int64
|
||||
failedRequests int64
|
||||
totalLatency int64
|
||||
minLatency int64 = int64(time.Hour)
|
||||
maxLatency int64
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
collector := &LatencyCollector{
|
||||
latencies: make([]int64, 0, 100000),
|
||||
}
|
||||
|
||||
// 高性能HTTP客户端
|
||||
client := &http.Client{
|
||||
Timeout: config.Timeout,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: config.Concurrency * 2,
|
||||
MaxIdleConnsPerHost: config.Concurrency * 2,
|
||||
MaxConnsPerHost: config.Concurrency * 2,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// 启动并发
|
||||
for i := 0; i < config.Concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
reqStart := time.Now()
|
||||
success := makeRequest(client, config.URL)
|
||||
latency := time.Since(reqStart).Nanoseconds()
|
||||
|
||||
atomic.AddInt64(&totalRequests, 1)
|
||||
atomic.AddInt64(&totalLatency, latency)
|
||||
|
||||
if success {
|
||||
atomic.AddInt64(&successRequests, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&failedRequests, 1)
|
||||
}
|
||||
|
||||
// 采样收集延迟(每100个请求采样1个,减少内存开销)
|
||||
if atomic.LoadInt64(&totalRequests)%100 == 0 {
|
||||
collector.Add(latency)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if latency < minLatency {
|
||||
minLatency = latency
|
||||
}
|
||||
if latency > maxLatency {
|
||||
maxLatency = latency
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(config.Duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
|
||||
totalDuration := time.Since(startTime)
|
||||
|
||||
result := BenchResult{
|
||||
TotalRequests: totalRequests,
|
||||
SuccessRequests: successRequests,
|
||||
FailedRequests: failedRequests,
|
||||
TotalDuration: totalDuration,
|
||||
MinLatency: minLatency,
|
||||
MaxLatency: maxLatency,
|
||||
P50Latency: collector.GetPercentile(0.50),
|
||||
P90Latency: collector.GetPercentile(0.90),
|
||||
P99Latency: collector.GetPercentile(0.99),
|
||||
}
|
||||
|
||||
if totalRequests > 0 {
|
||||
result.AvgLatency = totalLatency / totalRequests
|
||||
result.QPS = float64(totalRequests) / totalDuration.Seconds()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func makeRequest(client *http.Client, url string) bool {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if status, ok := result["status"].(float64); !ok || status != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func printResult(result BenchResult) {
|
||||
successRate := float64(result.SuccessRequests) / float64(result.TotalRequests) * 100
|
||||
|
||||
fmt.Printf("总请求数: %d\n", result.TotalRequests)
|
||||
fmt.Printf("成功请求: %d\n", result.SuccessRequests)
|
||||
fmt.Printf("失败请求: %d\n", result.FailedRequests)
|
||||
fmt.Printf("成功率: %.2f%%\n", successRate)
|
||||
fmt.Printf("总耗时: %v\n", result.TotalDuration.Round(time.Millisecond))
|
||||
fmt.Printf("QPS: %.2f 请求/秒\n", result.QPS)
|
||||
fmt.Println("------------------------------------------")
|
||||
fmt.Printf("最小延迟: %v\n", time.Duration(result.MinLatency).Round(time.Microsecond))
|
||||
fmt.Printf("平均延迟: %v\n", time.Duration(result.AvgLatency).Round(time.Microsecond))
|
||||
fmt.Printf("P50延迟: %v\n", time.Duration(result.P50Latency).Round(time.Microsecond))
|
||||
fmt.Printf("P90延迟: %v\n", time.Duration(result.P90Latency).Round(time.Microsecond))
|
||||
fmt.Printf("P99延迟: %v\n", time.Duration(result.P99Latency).Round(time.Microsecond))
|
||||
fmt.Printf("最大延迟: %v\n", time.Duration(result.MaxLatency).Round(time.Microsecond))
|
||||
|
||||
// 性能评级
|
||||
fmt.Print("\n性能评级: ")
|
||||
switch {
|
||||
case result.QPS >= 200000:
|
||||
fmt.Println("🏆 卓越 (QPS >= 200K)")
|
||||
case result.QPS >= 100000:
|
||||
fmt.Println("🚀 优秀 (QPS >= 100K)")
|
||||
case result.QPS >= 50000:
|
||||
fmt.Println("⭐ 良好 (QPS >= 50K)")
|
||||
case result.QPS >= 20000:
|
||||
fmt.Println("👍 中上 (QPS >= 20K)")
|
||||
case result.QPS >= 10000:
|
||||
fmt.Println("📊 中等 (QPS >= 10K)")
|
||||
default:
|
||||
fmt.Println("⚠️ 一般 (QPS < 10K)")
|
||||
}
|
||||
}
|
||||
|
||||
func estimateUsers(maxQPS float64) {
|
||||
fmt.Println("📈 并发用户数估算(基于不同使用场景):")
|
||||
fmt.Println("------------------------------------------")
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
requestInterval float64 // 用户平均请求间隔(秒)
|
||||
}{
|
||||
{"高频交互(每秒1次请求)", 1},
|
||||
{"活跃用户(每5秒1次请求)", 5},
|
||||
{"普通浏览(每10秒1次请求)", 10},
|
||||
{"低频访问(每30秒1次请求)", 30},
|
||||
{"偶尔访问(每60秒1次请求)", 60},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
users := maxQPS * s.requestInterval
|
||||
fmt.Printf("%-30s ~%d 用户\n", s.name, int(users))
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("💡 实际生产环境建议保留 30-50% 性能余量")
|
||||
fmt.Printf(" 安全并发用户数: %d - %d (普通浏览场景)\n",
|
||||
int(maxQPS*10*0.5), int(maxQPS*10*0.7))
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
fmt.Println("==========================================")
|
||||
fmt.Println(" 🔥 HoTime 极限压力测试 🔥")
|
||||
fmt.Println("==========================================")
|
||||
fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
|
||||
fmt.Println()
|
||||
|
||||
baseURL := "http://127.0.0.1:8081/app/test/hello"
|
||||
|
||||
// 检查服务
|
||||
fmt.Println("正在检查服务是否可用...")
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(baseURL)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
fmt.Println("❌ 服务不可用,请先启动示例应用")
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
fmt.Println("✅ 服务已就绪")
|
||||
fmt.Println()
|
||||
|
||||
// 预热
|
||||
fmt.Println("🔄 预热中 (5秒)...")
|
||||
doWarmup(baseURL, 100, 5*time.Second)
|
||||
fmt.Println("✅ 预热完成")
|
||||
fmt.Println()
|
||||
|
||||
// 极限测试配置
|
||||
concurrencyLevels := []int{500, 1000, 2000, 5000, 10000}
|
||||
testDuration := 15 * time.Second
|
||||
|
||||
var maxQPS float64
|
||||
var maxConcurrency int
|
||||
|
||||
for i, concurrency := range concurrencyLevels {
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
fmt.Printf("【极限测试 %d】并发数: %d, 持续时间: %v\n", i+1, concurrency, testDuration)
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
|
||||
total, success, failed, qps, minLat, avgLat, maxLat, p50, p90, p99 := doBenchmark(baseURL, concurrency, testDuration)
|
||||
|
||||
successRate := float64(success) / float64(total) * 100
|
||||
|
||||
fmt.Printf("总请求数: %d\n", total)
|
||||
fmt.Printf("成功请求: %d\n", success)
|
||||
fmt.Printf("失败请求: %d\n", failed)
|
||||
fmt.Printf("成功率: %.2f%%\n", successRate)
|
||||
fmt.Printf("QPS: %.2f 请求/秒\n", qps)
|
||||
fmt.Println("------------------------------------------")
|
||||
fmt.Printf("最小延迟: %v\n", time.Duration(minLat))
|
||||
fmt.Printf("平均延迟: %v\n", time.Duration(avgLat))
|
||||
fmt.Printf("P50延迟: %v\n", time.Duration(p50))
|
||||
fmt.Printf("P90延迟: %v\n", time.Duration(p90))
|
||||
fmt.Printf("P99延迟: %v\n", time.Duration(p99))
|
||||
fmt.Printf("最大延迟: %v\n", time.Duration(maxLat))
|
||||
|
||||
// 性能评级
|
||||
fmt.Print("\n性能评级: ")
|
||||
switch {
|
||||
case qps >= 200000:
|
||||
fmt.Println("🏆 卓越 (QPS >= 200K)")
|
||||
case qps >= 100000:
|
||||
fmt.Println("🚀 优秀 (QPS >= 100K)")
|
||||
case qps >= 50000:
|
||||
fmt.Println("⭐ 良好 (QPS >= 50K)")
|
||||
case qps >= 20000:
|
||||
fmt.Println("👍 中上 (QPS >= 20K)")
|
||||
case qps >= 10000:
|
||||
fmt.Println("📊 中等 (QPS >= 10K)")
|
||||
default:
|
||||
fmt.Println("⚠️ 一般 (QPS < 10K)")
|
||||
}
|
||||
|
||||
if qps > maxQPS {
|
||||
maxQPS = qps
|
||||
maxConcurrency = concurrency
|
||||
}
|
||||
|
||||
// 检查是否达到瓶颈
|
||||
if successRate < 95 {
|
||||
fmt.Println("\n⚠️ 成功率低于95%,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
if avgLat > int64(100*time.Millisecond) {
|
||||
fmt.Println("\n⚠️ 平均延迟超过100ms,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
if i < len(concurrencyLevels)-1 {
|
||||
fmt.Println("冷却 5 秒...")
|
||||
time.Sleep(5 * time.Second)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// 最终报告
|
||||
fmt.Println()
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Println(" 📊 极限测试总结")
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Printf("最高 QPS: %.2f 请求/秒\n", maxQPS)
|
||||
fmt.Printf("最佳并发数: %d\n", maxConcurrency)
|
||||
fmt.Println()
|
||||
|
||||
// 并发用户估算
|
||||
fmt.Println("📈 并发用户数估算:")
|
||||
fmt.Println("------------------------------------------")
|
||||
fmt.Printf("高频交互(1秒/次): ~%d 用户\n", int(maxQPS*1))
|
||||
fmt.Printf("活跃用户(5秒/次): ~%d 用户\n", int(maxQPS*5))
|
||||
fmt.Printf("普通浏览(10秒/次): ~%d 用户\n", int(maxQPS*10))
|
||||
fmt.Printf("低频访问(30秒/次): ~%d 用户\n", int(maxQPS*30))
|
||||
fmt.Println()
|
||||
fmt.Println("💡 生产环境建议保留 30-50% 性能余量")
|
||||
fmt.Printf(" 安全并发用户数: %d - %d (普通浏览场景)\n",
|
||||
int(maxQPS*10*0.5), int(maxQPS*10*0.7))
|
||||
}
|
||||
|
||||
func doWarmup(url string, concurrency int, duration time.Duration) {
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: concurrency * 2,
|
||||
MaxIdleConnsPerHost: concurrency * 2,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func doBenchmark(url string, concurrency int, duration time.Duration) (total, success, failed int64, qps float64, minLat, avgLat, maxLat, p50, p90, p99 int64) {
|
||||
var totalLatency int64
|
||||
minLat = int64(time.Hour)
|
||||
var mu sync.Mutex
|
||||
|
||||
// 采样收集器
|
||||
latencies := make([]int64, 0, 50000)
|
||||
var latMu sync.Mutex
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: concurrency * 2,
|
||||
MaxIdleConnsPerHost: concurrency * 2,
|
||||
MaxConnsPerHost: concurrency * 2,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
startTime := time.Now()
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
reqStart := time.Now()
|
||||
ok := doRequest(client, url)
|
||||
lat := time.Since(reqStart).Nanoseconds()
|
||||
|
||||
atomic.AddInt64(&total, 1)
|
||||
atomic.AddInt64(&totalLatency, lat)
|
||||
|
||||
if ok {
|
||||
atomic.AddInt64(&success, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&failed, 1)
|
||||
}
|
||||
|
||||
// 采样 (每50个采1个)
|
||||
if atomic.LoadInt64(&total)%50 == 0 {
|
||||
latMu.Lock()
|
||||
latencies = append(latencies, lat)
|
||||
latMu.Unlock()
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if lat < minLat {
|
||||
minLat = lat
|
||||
}
|
||||
if lat > maxLat {
|
||||
maxLat = lat
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
|
||||
totalDuration := time.Since(startTime)
|
||||
|
||||
if total > 0 {
|
||||
avgLat = totalLatency / total
|
||||
qps = float64(total) / totalDuration.Seconds()
|
||||
}
|
||||
|
||||
// 计算百分位
|
||||
if len(latencies) > 0 {
|
||||
p50 = getPercentile(latencies, 0.50)
|
||||
p90 = getPercentile(latencies, 0.90)
|
||||
p99 = getPercentile(latencies, 0.99)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func doRequest(client *http.Client, url string) bool {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if status, ok := result["status"].(float64); !ok || status != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getPercentile(arr []int64, p float64) int64 {
|
||||
n := len(arr)
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 复制一份避免修改原数组
|
||||
tmp := make([]int64, n)
|
||||
copy(tmp, arr)
|
||||
|
||||
idx := int(float64(n) * p)
|
||||
if idx >= n {
|
||||
idx = n - 1
|
||||
}
|
||||
|
||||
return quickSelect(tmp, idx)
|
||||
}
|
||||
|
||||
func quickSelect(arr []int64, k int) int64 {
|
||||
if len(arr) == 1 {
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
pivot := arr[len(arr)/2]
|
||||
var left, right, equal []int64
|
||||
|
||||
for _, v := range arr {
|
||||
if v < pivot {
|
||||
left = append(left, v)
|
||||
} else if v > pivot {
|
||||
right = append(right, v)
|
||||
} else {
|
||||
equal = append(equal, v)
|
||||
}
|
||||
}
|
||||
|
||||
if k < len(left) {
|
||||
return quickSelect(left, k)
|
||||
} else if k < len(left)+len(equal) {
|
||||
return pivot
|
||||
}
|
||||
return quickSelect(right, k-len(left)-len(equal))
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"aliyunCode": "06c6a07e89dd45c88de040ee1489eef7",
|
||||
"avatarPath": "avatar/2006/01/02/",
|
||||
"cache": {
|
||||
"db": {
|
||||
"db": false,
|
||||
"session": true
|
||||
},
|
||||
"memory": {
|
||||
"db": true,
|
||||
"session": true,
|
||||
"timeout": 7200
|
||||
}
|
||||
},
|
||||
"codeConfig": [
|
||||
{
|
||||
"config": "config/admin.json",
|
||||
"mode": 0,
|
||||
"name": "",
|
||||
"rule": "config/adminRule.json",
|
||||
"table": "admin"
|
||||
}
|
||||
],
|
||||
"crossDomain": "",
|
||||
"db": {
|
||||
"mysql": {
|
||||
"host": "192.168.2.50",
|
||||
"name": "zct_v2_v2",
|
||||
"password": "kct@2021",
|
||||
"port": "3306",
|
||||
"prefix": "",
|
||||
"user": "root"
|
||||
}
|
||||
},
|
||||
"defFile": [
|
||||
"index.html",
|
||||
"index.htm"
|
||||
],
|
||||
"error": {
|
||||
"1": "内部系统异常",
|
||||
"2": "访问权限异常",
|
||||
"3": "请求参数异常",
|
||||
"4": "数据处理异常",
|
||||
"5": "数据结果异常",
|
||||
"6": "需要进一步获取个人信息"
|
||||
},
|
||||
"imgPath": "img/2006/01/02/",
|
||||
"mode": 2,
|
||||
"port": "8081",
|
||||
"sessionName": "HOTIME",
|
||||
"smsKey": "b0eb4bf0198b9983cffcb85b69fdf4fa",
|
||||
"smsLogin": "【政策通】您的验证码为:{code},请在5分钟内使用,切勿将验证码泄露于他人,如非本人操作请忽略。",
|
||||
"tencentId": "AKIDOgT8cKCQksnY7yKATaYO7j9ORJzSYohP",
|
||||
"tencentKey": "GNXgjdN4czA9ya0FNMApVJzTmsmU0KSN",
|
||||
"tpt": "tpt",
|
||||
"wechatAppID": "wxdcc8d6360661a179",
|
||||
"wechatAppNotifyUrl": "https://zcth5.kct.cn/app/vip_order/callback",
|
||||
"wechatAppSecret": "4d793683ca915264663a9c9a33530c3c",
|
||||
"wechatMiniAppID": "wx1c795e883b5b54c4",
|
||||
"wechatMiniAppSecret": "d2bec12d1fa4d8b5714ccbed1c0671e4",
|
||||
"wechatPayMApiV3Key": "dh33tyagd1623623GDYGGDhe1d9dh171",
|
||||
"wechatPayMCHID": "1624888906",
|
||||
"wechatPayPrivateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1vs3i/4sozxsK\noiS2S95rl+csLXQDugg23bcAVzXr7ZeTM/h81sCwraQDMnAJ/V3n7LxFAZeaFwLb\nyrkQ3lv4IEtWVgjVUkjkhvKGWcAp16Q/grOpxWmmn+VlW5ZGwQQ4DL4sC6BeEyxu\nUdtZ7UVc9lqsQX01R0oiegItJGPMMXNgeLFDEeoAwyIQcL0VG2bND7qrEVeQkLTj\nFm9ZkSb0LySPkSgqxMlSpiX1MS+wWWIHpq91CvdVHNowaGFA7ajU3RztbFAuxdpl\ng9RIocbrY1QwGXouQTEOUI2KZaES9rAa2lD6oWom4mRYiQ1oNO12XlUTcKsr84P5\nTq50dOYFAgMBAAECggEAQUns/mncnOlhhn1fANnaaf5kvlsJvTj8MHGPhyDNLxbo\nB6p4zqf7Cr0mGTvqQbxyGpnRvFxpEKLJlRmLSAMJOOapCbfYboGjy+yqfRcK0D02\nNNaIIinX3VK9fp7bKkm2cUgqnPoEPydWI79mNDTnYRDi29Se3R/iAuafl4XmD/js\nOKWG5tlXEatZ+gDbhnug4hp3xEGm4pbTS1HedV7HgYiKpxAT9uc/YmiHelZUUqlG\nwa1Nda2J88pzCSZX6TsRyY9UF/sjorxSNZGfj+5waeYMccj0Cx4uEQYgBv5tm+I+\nFTRgo/riM5gT9oRTnuNuzUrNwUNLyUsldvdQdlheNQKBgQDqwhrW52q3xClFLXR4\nHu9cTPt/p96MYE+xBllO/M9VFcG/hT++9R9l26+o26Lu+iIF5euDQiYSyURniGim\nWfV0I2q8HufhPLJBamvxRPI0V8LHwbY7c6HKnRR3eDAWAVYIcmsV4RLS67bsgtnr\nCgaFUDHXnpPKjxp1b4M5K3GqxwKBgQDGMLgAQn0a3nZwquPD4D+6tjy5xTxDX4Op\nULHBQheX0lBAR9Kcurzy4ca+T5xsyHd6SPwQxobRPei/3TBmc18J1T3nxJkTbEEP\nqy8k7MOWW2es3ou4CRL5lVKPx4YDWb5iT7X3Ue55JjPFAoj6FBOHxdvRQnDYsLFy\nSuSHPc280wKBgQCqfvCZNZcnAbtrd3jIKMd0hKB/dP7HesdF7TN9j1RRGi0NmIvU\ndxgnlOa9v05VO6rsF7D1MlyOdkhM3SAL+PewMmy5VcTYq4lWwyDEKGuzoi1fgIuG\nIBPYID8WCV77DFtcZSTqzf0q3HCM0vfLoQtdVQHt9Ein60ivE579LVUvTwKBgFid\nefgrwnJcG8serb5sKzKZvyc1CE/7igwPl5sYqSHqGJXVR1dqq4dR6iI3yHJfZASa\nU5JQogE21DXNeZGlbk4gOZDCt8sWcTTHTsoMzxsQfZeu3fwImqJb4NGG3eXrn5On\nnm4aBS3IJgelrYdbqKvhjPrQ4VISFxVKZUoPGUmfAoGARZbtbyl3s8cAExWe9dK6\nyWdkA3M2wR4n623W13rTQDc3D7p/hmlgB2x8it88m+580326G4qgwdUbG93EW1WQ\nahbkkcmdxzHgPVJdsyx22fR0TkeT1gjMpoSh4k3HOsbZE4EvlI459yE9fTSLBq2A\nFzGjYRpBTlRyrsSEhyVg4G0=\n-----END PRIVATE KEY-----",
|
||||
"wechatPaySerialNo": "7A92D2D26D212D6BF934BDB10D547274807C3DDB",
|
||||
"wxFilePath": "wxfile/2006/01/02/"
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"cache": {
|
||||
"db": {
|
||||
"db": "默认false,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 24 * 30,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"memory": {
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 2,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"redis": {
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"host": "默认服务ip:127.0.0.1,必须,如果需要使用redis服务时配置,",
|
||||
"password": "默认密码空,必须,如果需要使用redis服务时配置,默认密码空",
|
||||
"port": "默认服务端口:6379,必须,如果需要使用redis服务时配置,",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 24 * 15,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"注释": "可配置memory,db,redis,默认启用memory,默认优先级为memory\u003eredis\u003edb,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用"
|
||||
},
|
||||
"codeConfig": [
|
||||
"注释:配置即启用,非必须,默认无",
|
||||
{
|
||||
"config": "默认config/app.json,必须,接口描述配置文件",
|
||||
"mode": "默认0,非必须,0为内嵌代码模式,1为生成代码模式",
|
||||
"name": "默认无,非必须,有则生成代码到此目录,无则采用缺省模式使用表名,如设置为:admin,将在admin目录生成包名为admin的代码",
|
||||
"rule": "默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成",
|
||||
"table": "默认admin,必须,根据数据库内当前表名做为用户生成数据"
|
||||
}
|
||||
],
|
||||
"crossDomain": "默认空 非必须,空字符串为不开启,如果需要跨域设置,auto为智能开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域",
|
||||
"db": {
|
||||
"mysql": {
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"prefix": "默认空,非必须,数据表前缀",
|
||||
"slave": {
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"注释": "从数据库配置,mysql里配置slave项即启用主从读写,减少数据库压力"
|
||||
},
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"注释": "除prefix及主从数据库slave项,其他全部必须"
|
||||
},
|
||||
"sqlite": {
|
||||
"path": "默认config/data.db,必须,数据库位置"
|
||||
},
|
||||
"注释": "配置即启用,非必须,默认使用sqlite数据库"
|
||||
},
|
||||
"defFile": "默认访问index.html或者index.htm文件,必须,默认访问文件类型",
|
||||
"error": {
|
||||
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
|
||||
"2": "访问权限异常,没有登录或者登录异常等时候使用",
|
||||
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
|
||||
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
|
||||
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
|
||||
"注释": "web服务内置错误提示,自定义异常建议10开始"
|
||||
},
|
||||
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"logLevel": "默认0,必须,0关闭,1打印,日志等级",
|
||||
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存",
|
||||
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
|
||||
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
|
||||
"sessionName": "默认HOTIME,必须,设置session的cookie名",
|
||||
"tlsCert": "默认空,非必须,https证书",
|
||||
"tlsKey": "默认空,非必须,https密钥",
|
||||
"tlsPort": "默认空,非必须,web服务https端口,0为不启用https服务",
|
||||
"tpt": "默认tpt,必须,web静态文件目录,默认为程序目录下tpt目录",
|
||||
"webConnectLogFile": "无默认,非必须,webConnectLogShow开启之后才能使用,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"webConnectLogShow": "默认true,非必须,访问日志如果需要web访问链接、访问ip、访问时间打印,false为关闭true开启此功能"
|
||||
}
|
||||
Binary file not shown.
+888
-72
@@ -2,88 +2,904 @@ package main
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
"code.hoteas.com/golang/hotime/dri/aliyun"
|
||||
"code.hoteas.com/golang/hotime/dri/ddsms"
|
||||
"code.hoteas.com/golang/hotime/dri/tencent"
|
||||
"code.hoteas.com/golang/hotime/dri/wechat"
|
||||
"code.hoteas.com/golang/hotime/example/app"
|
||||
"code.hoteas.com/golang/hotime/example/provider"
|
||||
//. "code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
)
|
||||
|
||||
//
|
||||
func main() {
|
||||
date, _ := time.Parse("2006-01-02 15:04", time.Now().Format("2006-01-02")+" 14:00")
|
||||
fmt.Println(date, date.Unix())
|
||||
//fmt.Println("开始测试")
|
||||
//t:=common.Map{"t":1652425167}
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]=1652425167123
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]=1652425167123456
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]=1652425167123456789
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//fmt.Println("开始测试2")
|
||||
//t["t"]="2006-01-02 15:04:05"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]="2006-01-02 15"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]="2006-01-02"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]="2006-01"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]="2006-01"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//t["t"]="2006"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
//
|
||||
//t["t"]="06"
|
||||
//fmt.Println(t.GetTime("t"))
|
||||
|
||||
//fmt.Println("0123456"[1:7])
|
||||
appIns := Init("config/config.json")
|
||||
|
||||
aliyun.Company.Init(appIns.Config.GetString("aliyunCode"))
|
||||
|
||||
//初始化短信配置
|
||||
ddsms.DDY.Init(appIns.Config.GetString("smsKey"))
|
||||
//初始化公众号配置
|
||||
wechat.H5Program.Init(appIns.Config.GetString("wechatAppID"), appIns.Config.GetString("wechatAppSecret"))
|
||||
//初始化小程序配置
|
||||
wechat.MiniProgram.Init(appIns.Config.GetString("wechatMiniAppID"), appIns.Config.GetString("wechatMiniAppSecret"))
|
||||
//初始化小程序及公众号支付配置
|
||||
wechat.WxPay.Init(appIns.Config.GetString("wechatPayMCHID"),
|
||||
appIns.Config.GetString("wechatPaySerialNo"),
|
||||
appIns.Config.GetString("wechatPayMApiV3Key"),
|
||||
appIns.Config.GetString("wechatPayPrivateKey"))
|
||||
tencent.Tencent.Init(appIns.Config.GetString("tencentId"), appIns.Config.GetString("tencentKey"))
|
||||
|
||||
//用户侧访问前设置
|
||||
appIns.SetConnectListener(func(that *Context) (isFinished bool) {
|
||||
//发送短信校验
|
||||
//that.RespFunc = func() {
|
||||
// go func(Form url.Values, RespData common.Map) {
|
||||
// fmt.Println(Form)
|
||||
// fmt.Println(RespData)
|
||||
// }(that.Req.Form, that.RespData)
|
||||
//
|
||||
//}
|
||||
|
||||
return isFinished
|
||||
})
|
||||
|
||||
//appIns.SetConnectListener(func(that *Context) (isFinished bool) {
|
||||
//
|
||||
// return isFinished
|
||||
//})
|
||||
//appIns.Db.Action(func(db db.HoTimeDB) (isSuccess bool) {
|
||||
// return isSuccess
|
||||
//})
|
||||
appIns.Run(Router{
|
||||
"provider": provider.Project,
|
||||
"app": app.Project,
|
||||
"app": {
|
||||
"test": {
|
||||
// 测试入口 - 运行所有测试
|
||||
"all": func(that *Context) {
|
||||
results := Map{}
|
||||
|
||||
// 初始化测试表
|
||||
initTestTables(that)
|
||||
|
||||
// 1. 基础 CRUD 测试
|
||||
results["1_basic_crud"] = testBasicCRUD(that)
|
||||
|
||||
// 2. 条件查询语法测试
|
||||
results["2_condition_syntax"] = testConditionSyntax(that)
|
||||
|
||||
// 3. 链式查询测试
|
||||
results["3_chain_query"] = testChainQuery(that)
|
||||
|
||||
// 4. JOIN 查询测试
|
||||
results["4_join_query"] = testJoinQuery(that)
|
||||
|
||||
// 5. 聚合函数测试
|
||||
results["5_aggregate"] = testAggregate(that)
|
||||
|
||||
// 6. 分页查询测试
|
||||
results["6_pagination"] = testPagination(that)
|
||||
|
||||
// 7. 批量插入测试
|
||||
results["7_batch_insert"] = testInserts(that)
|
||||
|
||||
// 8. Upsert 测试
|
||||
results["8_upsert"] = testUpsert(that)
|
||||
|
||||
// 9. 事务测试
|
||||
results["9_transaction"] = testTransaction(that)
|
||||
|
||||
// 10. 原生 SQL 测试
|
||||
results["10_raw_sql"] = testRawSQL(that)
|
||||
|
||||
that.Display(0, results)
|
||||
},
|
||||
|
||||
// 查询数据库表结构
|
||||
"tables": func(that *Context) {
|
||||
// 查询所有表
|
||||
tables := that.Db.Query("SHOW TABLES")
|
||||
that.Display(0, Map{"tables": tables})
|
||||
},
|
||||
|
||||
// 查询指定表的结构
|
||||
"describe": func(that *Context) {
|
||||
tableName := that.Req.FormValue("table")
|
||||
if tableName == "" {
|
||||
that.Display(1, "请提供 table 参数")
|
||||
return
|
||||
}
|
||||
// 查询表结构
|
||||
columns := that.Db.Query("DESCRIBE " + tableName)
|
||||
// 查询表数据(前10条)
|
||||
data := that.Db.Select(tableName, Map{"LIMIT": 10})
|
||||
that.Display(0, Map{"table": tableName, "columns": columns, "sample_data": data})
|
||||
},
|
||||
|
||||
// 单独测试入口
|
||||
"crud": func(that *Context) { that.Display(0, testBasicCRUD(that)) },
|
||||
"condition": func(that *Context) { that.Display(0, testConditionSyntax(that)) },
|
||||
"chain": func(that *Context) { that.Display(0, testChainQuery(that)) },
|
||||
"join": func(that *Context) { that.Display(0, testJoinQuery(that)) },
|
||||
"aggregate": func(that *Context) { that.Display(0, testAggregate(that)) },
|
||||
"pagination": func(that *Context) { that.Display(0, testPagination(that)) },
|
||||
"batch": func(that *Context) { that.Display(0, testInserts(that)) },
|
||||
"upsert": func(that *Context) { that.Display(0, testUpsert(that)) },
|
||||
"transaction": func(that *Context) { that.Display(0, testTransaction(that)) },
|
||||
"rawsql": func(that *Context) { that.Display(0, testRawSQL(that)) },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// initTestTables 初始化测试表(MySQL真实数据库)
|
||||
func initTestTables(that *Context) {
|
||||
// MySQL 真实数据库已有表,创建测试用的临时表
|
||||
// 创建测试用的批量插入表
|
||||
that.Db.Exec(`CREATE TABLE IF NOT EXISTS test_batch (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100),
|
||||
title VARCHAR(200),
|
||||
state INT DEFAULT 0,
|
||||
create_time DATETIME
|
||||
)`)
|
||||
|
||||
// 检查 admin 表数据(确认表存在)
|
||||
_ = that.Db.Count("admin")
|
||||
_ = that.Db.Count("article")
|
||||
}
|
||||
|
||||
// ==================== 1. 基础 CRUD 测试 ====================
|
||||
func testBasicCRUD(that *Context) Map {
|
||||
result := Map{"name": "基础CRUD测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 1.1 Insert 测试 - 使用 admin 表
|
||||
insertTest := Map{"name": "Insert 插入测试 (admin表)"}
|
||||
adminId := that.Db.Insert("admin", Map{
|
||||
"name": "测试管理员_" + fmt.Sprintf("%d", time.Now().Unix()),
|
||||
"phone": fmt.Sprintf("138%d", time.Now().Unix()%100000000),
|
||||
"state": 1,
|
||||
"password": "test123456",
|
||||
"role_id": 1,
|
||||
"title": "测试职位",
|
||||
"create_time[#]": "NOW()",
|
||||
"modify_time[#]": "NOW()",
|
||||
})
|
||||
insertTest["result"] = adminId > 0
|
||||
insertTest["adminId"] = adminId
|
||||
insertTest["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, insertTest)
|
||||
|
||||
// 1.2 Get 测试
|
||||
getTest := Map{"name": "Get 获取单条记录测试"}
|
||||
admin := that.Db.Get("admin", "*", Map{"id": adminId})
|
||||
getTest["result"] = admin != nil && admin.GetInt64("id") == adminId
|
||||
getTest["admin"] = admin
|
||||
tests = append(tests, getTest)
|
||||
|
||||
// 1.3 Select 测试 - 单条件
|
||||
selectTest1 := Map{"name": "Select 单条件查询测试"}
|
||||
admins1 := that.Db.Select("admin", "*", Map{"state": 1, "LIMIT": 5})
|
||||
selectTest1["result"] = len(admins1) >= 0 // 可能表中没有数据
|
||||
selectTest1["count"] = len(admins1)
|
||||
tests = append(tests, selectTest1)
|
||||
|
||||
// 1.4 Select 测试 - 多条件(自动 AND)
|
||||
selectTest2 := Map{"name": "Select 多条件自动AND测试"}
|
||||
admins2 := that.Db.Select("admin", "*", Map{
|
||||
"state": 1,
|
||||
"role_id[>]": 0,
|
||||
"ORDER": "id DESC",
|
||||
"LIMIT": 5,
|
||||
})
|
||||
selectTest2["result"] = true // 只要不报错就算成功
|
||||
selectTest2["count"] = len(admins2)
|
||||
selectTest2["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, selectTest2)
|
||||
|
||||
// 1.5 Update 测试
|
||||
updateTest := Map{"name": "Update 更新测试"}
|
||||
affected := that.Db.Update("admin", Map{
|
||||
"title": "更新后的职位",
|
||||
"modify_time[#]": "NOW()",
|
||||
}, Map{"id": adminId})
|
||||
updateTest["result"] = affected > 0
|
||||
updateTest["affected"] = affected
|
||||
tests = append(tests, updateTest)
|
||||
|
||||
// 1.6 Delete 测试 - 使用 test_batch 表
|
||||
deleteTest := Map{"name": "Delete 删除测试"}
|
||||
// 先创建一个临时记录用于删除
|
||||
tempId := that.Db.Insert("test_batch", Map{
|
||||
"name": "临时删除测试",
|
||||
"title": "测试标题",
|
||||
"state": 1,
|
||||
"create_time[#]": "NOW()",
|
||||
})
|
||||
deleteAffected := that.Db.Delete("test_batch", Map{"id": tempId})
|
||||
deleteTest["result"] = deleteAffected > 0
|
||||
deleteTest["affected"] = deleteAffected
|
||||
tests = append(tests, deleteTest)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 2. 条件查询语法测试 ====================
|
||||
func testConditionSyntax(that *Context) Map {
|
||||
result := Map{"name": "条件查询语法测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 2.1 等于 (=) - 使用 article 表
|
||||
test1 := Map{"name": "等于条件 (=)"}
|
||||
articles1 := that.Db.Select("article", "id,title", Map{"state": 0, "LIMIT": 3})
|
||||
test1["result"] = true
|
||||
test1["count"] = len(articles1)
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 2.2 不等于 ([!])
|
||||
test2 := Map{"name": "不等于条件 ([!])"}
|
||||
articles2 := that.Db.Select("article", "id,title,state", Map{"state[!]": -1, "LIMIT": 3})
|
||||
test2["result"] = true
|
||||
test2["count"] = len(articles2)
|
||||
test2["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 2.3 大于 ([>]) 和 小于 ([<])
|
||||
test3 := Map{"name": "大于小于条件 ([>], [<])"}
|
||||
articles3 := that.Db.Select("article", "id,title,click_num", Map{
|
||||
"click_num[>]": 0,
|
||||
"click_num[<]": 100000,
|
||||
"LIMIT": 3,
|
||||
})
|
||||
test3["result"] = true
|
||||
test3["count"] = len(articles3)
|
||||
tests = append(tests, test3)
|
||||
|
||||
// 2.4 大于等于 ([>=]) 和 小于等于 ([<=])
|
||||
test4 := Map{"name": "大于等于小于等于条件 ([>=], [<=])"}
|
||||
articles4 := that.Db.Select("article", "id,title,sort", Map{
|
||||
"sort[>=]": 0,
|
||||
"sort[<=]": 100,
|
||||
"LIMIT": 3,
|
||||
})
|
||||
test4["result"] = true
|
||||
test4["count"] = len(articles4)
|
||||
tests = append(tests, test4)
|
||||
|
||||
// 2.5 LIKE 模糊查询 ([~])
|
||||
test5 := Map{"name": "LIKE 模糊查询 ([~])"}
|
||||
articles5 := that.Db.Select("article", "id,title", Map{"title[~]": "新闻", "LIMIT": 3})
|
||||
test5["result"] = true
|
||||
test5["count"] = len(articles5)
|
||||
test5["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test5)
|
||||
|
||||
// 2.6 右模糊 ([~!])
|
||||
test6 := Map{"name": "右模糊查询 ([~!])"}
|
||||
articles6 := that.Db.Select("admin", "id,name", Map{"name[~!]": "管理", "LIMIT": 3})
|
||||
test6["result"] = true
|
||||
test6["count"] = len(articles6)
|
||||
tests = append(tests, test6)
|
||||
|
||||
// 2.7 BETWEEN ([<>])
|
||||
test7 := Map{"name": "BETWEEN 区间查询 ([<>])"}
|
||||
articles7 := that.Db.Select("article", "id,title,click_num", Map{"click_num[<>]": Slice{0, 1000}, "LIMIT": 3})
|
||||
test7["result"] = true
|
||||
test7["count"] = len(articles7)
|
||||
test7["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test7)
|
||||
|
||||
// 2.8 NOT BETWEEN ([><])
|
||||
test8 := Map{"name": "NOT BETWEEN 查询 ([><])"}
|
||||
articles8 := that.Db.Select("article", "id,title,sort", Map{"sort[><]": Slice{-10, 0}, "LIMIT": 3})
|
||||
test8["result"] = true
|
||||
test8["count"] = len(articles8)
|
||||
tests = append(tests, test8)
|
||||
|
||||
// 2.9 IN 查询
|
||||
test9 := Map{"name": "IN 查询"}
|
||||
articles9 := that.Db.Select("article", "id,title", Map{"id": Slice{1, 2, 3, 4, 5}, "LIMIT": 5})
|
||||
test9["result"] = true
|
||||
test9["count"] = len(articles9)
|
||||
test9["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test9)
|
||||
|
||||
// 2.10 NOT IN ([!])
|
||||
test10 := Map{"name": "NOT IN 查询 ([!])"}
|
||||
articles10 := that.Db.Select("article", "id,title", Map{"id[!]": Slice{1, 2, 3}, "LIMIT": 5})
|
||||
test10["result"] = true
|
||||
test10["count"] = len(articles10)
|
||||
tests = append(tests, test10)
|
||||
|
||||
// 2.11 IS NULL
|
||||
test11 := Map{"name": "IS NULL 查询"}
|
||||
articles11 := that.Db.Select("article", "id,title,img", Map{"img": nil, "LIMIT": 3})
|
||||
test11["result"] = true
|
||||
test11["count"] = len(articles11)
|
||||
tests = append(tests, test11)
|
||||
|
||||
// 2.12 IS NOT NULL ([!])
|
||||
test12 := Map{"name": "IS NOT NULL 查询 ([!])"}
|
||||
articles12 := that.Db.Select("article", "id,title,create_time", Map{"create_time[!]": nil, "LIMIT": 3})
|
||||
test12["result"] = true
|
||||
test12["count"] = len(articles12)
|
||||
tests = append(tests, test12)
|
||||
|
||||
// 2.13 直接 SQL ([##] 用于 SQL 片段)
|
||||
test13 := Map{"name": "直接 SQL 片段查询 ([##])"}
|
||||
articles13 := that.Db.Select("article", "id,title,create_time", Map{
|
||||
"[##]": "create_time > DATE_SUB(NOW(), INTERVAL 365 DAY)",
|
||||
"LIMIT": 3,
|
||||
})
|
||||
test13["result"] = true
|
||||
test13["count"] = len(articles13)
|
||||
test13["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test13)
|
||||
|
||||
// 2.14 显式 AND 条件
|
||||
test14 := Map{"name": "显式 AND 条件"}
|
||||
articles14 := that.Db.Select("article", "id,title,state,click_num", Map{
|
||||
"AND": Map{
|
||||
"state": 0,
|
||||
"click_num[>=]": 0,
|
||||
},
|
||||
"LIMIT": 3,
|
||||
})
|
||||
test14["result"] = true
|
||||
test14["count"] = len(articles14)
|
||||
tests = append(tests, test14)
|
||||
|
||||
// 2.15 OR 条件
|
||||
test15 := Map{"name": "OR 条件"}
|
||||
articles15 := that.Db.Select("article", "id,title,sort,click_num", Map{
|
||||
"OR": Map{
|
||||
"sort": 0,
|
||||
"click_num[>]": 10,
|
||||
},
|
||||
"LIMIT": 5,
|
||||
})
|
||||
test15["result"] = true
|
||||
test15["count"] = len(articles15)
|
||||
tests = append(tests, test15)
|
||||
|
||||
// 2.16 嵌套 AND/OR 条件
|
||||
test16 := Map{"name": "嵌套 AND/OR 条件"}
|
||||
articles16 := that.Db.Select("article", "id,title,sort,state", Map{
|
||||
"AND": Map{
|
||||
"state": 0,
|
||||
"OR": Map{
|
||||
"sort[>=]": 0,
|
||||
"click_num[>]": 0,
|
||||
},
|
||||
},
|
||||
"LIMIT": 5,
|
||||
})
|
||||
test16["result"] = true
|
||||
test16["count"] = len(articles16)
|
||||
test16["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test16)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 3. 链式查询测试 ====================
|
||||
func testChainQuery(that *Context) Map {
|
||||
result := Map{"name": "链式查询测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 3.1 基本链式查询 - 使用 article 表
|
||||
test1 := Map{"name": "基本链式查询 Table().Where().Select()"}
|
||||
articles1 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Select("id,title,author")
|
||||
test1["result"] = len(articles1) >= 0
|
||||
test1["count"] = len(articles1)
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 3.2 链式 And 条件
|
||||
test2 := Map{"name": "链式 And 条件"}
|
||||
articles2 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
And("click_num[>=]", 0).
|
||||
And("sort[>=]", 0).
|
||||
Select("id,title,click_num")
|
||||
test2["result"] = len(articles2) >= 0
|
||||
test2["count"] = len(articles2)
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 3.3 链式 Or 条件
|
||||
test3 := Map{"name": "链式 Or 条件"}
|
||||
articles3 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Or(Map{
|
||||
"sort": 0,
|
||||
"click_num[>]": 10,
|
||||
}).
|
||||
Select("id,title,sort,click_num")
|
||||
test3["result"] = len(articles3) >= 0
|
||||
test3["count"] = len(articles3)
|
||||
tests = append(tests, test3)
|
||||
|
||||
// 3.4 链式 Order
|
||||
test4 := Map{"name": "链式 Order 排序"}
|
||||
articles4 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Order("create_time DESC", "id ASC").
|
||||
Limit(0, 5).
|
||||
Select("id,title,create_time")
|
||||
test4["result"] = len(articles4) >= 0
|
||||
test4["count"] = len(articles4)
|
||||
tests = append(tests, test4)
|
||||
|
||||
// 3.5 链式 Limit
|
||||
test5 := Map{"name": "链式 Limit 限制"}
|
||||
articles5 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Limit(0, 3).
|
||||
Select("id,title")
|
||||
test5["result"] = len(articles5) <= 3
|
||||
test5["count"] = len(articles5)
|
||||
tests = append(tests, test5)
|
||||
|
||||
// 3.6 链式 Get 单条
|
||||
test6 := Map{"name": "链式 Get 获取单条"}
|
||||
article6 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Get("id,title,author")
|
||||
test6["result"] = article6 != nil || true // 允许为空
|
||||
test6["article"] = article6
|
||||
tests = append(tests, test6)
|
||||
|
||||
// 3.7 链式 Count
|
||||
test7 := Map{"name": "链式 Count 统计"}
|
||||
count7 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Count()
|
||||
test7["result"] = count7 >= 0
|
||||
test7["count"] = count7
|
||||
tests = append(tests, test7)
|
||||
|
||||
// 3.8 链式 Page 分页
|
||||
test8 := Map{"name": "链式 Page 分页"}
|
||||
articles8 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Page(1, 5).
|
||||
Select("id,title")
|
||||
test8["result"] = len(articles8) <= 5
|
||||
test8["count"] = len(articles8)
|
||||
tests = append(tests, test8)
|
||||
|
||||
// 3.9 链式 Group 分组
|
||||
test9 := Map{"name": "链式 Group 分组"}
|
||||
stats9 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Group("ctg_id").
|
||||
Select("ctg_id, COUNT(*) as cnt")
|
||||
test9["result"] = len(stats9) >= 0
|
||||
test9["stats"] = stats9
|
||||
tests = append(tests, test9)
|
||||
|
||||
// 3.10 链式 Update
|
||||
test10 := Map{"name": "链式 Update 更新"}
|
||||
// 先获取一个文章ID
|
||||
testArticle := that.Db.Table("article").Where("state", 0).Get("id")
|
||||
if testArticle != nil {
|
||||
affected := that.Db.Table("article").
|
||||
Where("id", testArticle.GetInt64("id")).
|
||||
Update(Map{"modify_time[#]": "NOW()"})
|
||||
test10["result"] = affected >= 0
|
||||
test10["affected"] = affected
|
||||
} else {
|
||||
test10["result"] = true
|
||||
test10["note"] = "无可用测试数据"
|
||||
}
|
||||
tests = append(tests, test10)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 4. JOIN 查询测试 ====================
|
||||
func testJoinQuery(that *Context) Map {
|
||||
result := Map{"name": "JOIN查询测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 4.1 LEFT JOIN 链式 - article 关联 ctg
|
||||
test1 := Map{"name": "LEFT JOIN 链式查询"}
|
||||
articles1 := that.Db.Table("article").
|
||||
LeftJoin("ctg", "article.ctg_id = ctg.id").
|
||||
Where("article.state", 0).
|
||||
Limit(0, 5).
|
||||
Select("article.id, article.title, ctg.name as ctg_name")
|
||||
test1["result"] = len(articles1) >= 0
|
||||
test1["count"] = len(articles1)
|
||||
test1["data"] = articles1
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 4.2 传统 JOIN 语法
|
||||
test2 := Map{"name": "传统 JOIN 语法"}
|
||||
articles2 := that.Db.Select("article",
|
||||
Slice{
|
||||
Map{"[>]ctg": "article.ctg_id = ctg.id"},
|
||||
},
|
||||
"article.id, article.title, ctg.name as ctg_name",
|
||||
Map{
|
||||
"article.state": 0,
|
||||
"LIMIT": 5,
|
||||
})
|
||||
test2["result"] = len(articles2) >= 0
|
||||
test2["count"] = len(articles2)
|
||||
test2["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 4.3 多表 JOIN - article 关联 ctg 和 admin
|
||||
test3 := Map{"name": "多表 JOIN"}
|
||||
articles3 := that.Db.Table("article").
|
||||
LeftJoin("ctg", "article.ctg_id = ctg.id").
|
||||
LeftJoin("admin", "article.admin_id = admin.id").
|
||||
Where("article.state", 0).
|
||||
Limit(0, 5).
|
||||
Select("article.id, article.title, ctg.name as ctg_name, admin.name as admin_name")
|
||||
test3["result"] = len(articles3) >= 0
|
||||
test3["count"] = len(articles3)
|
||||
test3["data"] = articles3
|
||||
tests = append(tests, test3)
|
||||
|
||||
// 4.4 INNER JOIN
|
||||
test4 := Map{"name": "INNER JOIN"}
|
||||
articles4 := that.Db.Table("article").
|
||||
InnerJoin("ctg", "article.ctg_id = ctg.id").
|
||||
Where("ctg.state", 0).
|
||||
Limit(0, 5).
|
||||
Select("article.id, article.title, ctg.name as ctg_name")
|
||||
test4["result"] = len(articles4) >= 0
|
||||
test4["count"] = len(articles4)
|
||||
tests = append(tests, test4)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 5. 聚合函数测试 ====================
|
||||
func testAggregate(that *Context) Map {
|
||||
result := Map{"name": "聚合函数测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 5.1 Count 总数
|
||||
test1 := Map{"name": "Count 总数统计"}
|
||||
count1 := that.Db.Count("article")
|
||||
test1["result"] = count1 >= 0
|
||||
test1["count"] = count1
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 5.2 Count 带条件
|
||||
test2 := Map{"name": "Count 条件统计"}
|
||||
count2 := that.Db.Count("article", Map{"state": 0})
|
||||
test2["result"] = count2 >= 0
|
||||
test2["count"] = count2
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 5.3 Sum 求和
|
||||
test3 := Map{"name": "Sum 求和"}
|
||||
sum3 := that.Db.Sum("article", "click_num", Map{"state": 0})
|
||||
test3["result"] = sum3 >= 0
|
||||
test3["sum"] = sum3
|
||||
tests = append(tests, test3)
|
||||
|
||||
// 5.4 Avg 平均值
|
||||
test4 := Map{"name": "Avg 平均值"}
|
||||
avg4 := that.Db.Avg("article", "click_num", Map{"state": 0})
|
||||
test4["result"] = avg4 >= 0
|
||||
test4["avg"] = avg4
|
||||
tests = append(tests, test4)
|
||||
|
||||
// 5.5 Max 最大值
|
||||
test5 := Map{"name": "Max 最大值"}
|
||||
max5 := that.Db.Max("article", "click_num", Map{"state": 0})
|
||||
test5["result"] = max5 >= 0
|
||||
test5["max"] = max5
|
||||
tests = append(tests, test5)
|
||||
|
||||
// 5.6 Min 最小值
|
||||
test6 := Map{"name": "Min 最小值"}
|
||||
min6 := that.Db.Min("article", "sort", Map{"state": 0})
|
||||
test6["result"] = true // sort 可能为 0
|
||||
test6["min"] = min6
|
||||
tests = append(tests, test6)
|
||||
|
||||
// 5.7 GROUP BY 分组统计
|
||||
test7 := Map{"name": "GROUP BY 分组统计"}
|
||||
stats7 := that.Db.Select("article",
|
||||
"ctg_id, COUNT(*) as article_count, AVG(click_num) as avg_clicks, SUM(click_num) as total_clicks",
|
||||
Map{
|
||||
"state": 0,
|
||||
"GROUP": "ctg_id",
|
||||
"ORDER": "article_count DESC",
|
||||
"LIMIT": 10,
|
||||
})
|
||||
test7["result"] = len(stats7) >= 0
|
||||
test7["stats"] = stats7
|
||||
tests = append(tests, test7)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 6. 分页查询测试 ====================
|
||||
func testPagination(that *Context) Map {
|
||||
result := Map{"name": "分页查询测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 6.1 PageSelect 分页查询
|
||||
test1 := Map{"name": "PageSelect 分页查询"}
|
||||
articles1 := that.Db.Page(1, 5).PageSelect("article", "*", Map{
|
||||
"state": 0,
|
||||
"ORDER": "id DESC",
|
||||
})
|
||||
test1["result"] = len(articles1) <= 5
|
||||
test1["count"] = len(articles1)
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 6.2 第二页
|
||||
test2 := Map{"name": "PageSelect 第二页"}
|
||||
articles2 := that.Db.Page(2, 5).PageSelect("article", "*", Map{
|
||||
"state": 0,
|
||||
"ORDER": "id DESC",
|
||||
})
|
||||
test2["result"] = len(articles2) <= 5
|
||||
test2["count"] = len(articles2)
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 6.3 链式分页
|
||||
test3 := Map{"name": "链式 Page 分页"}
|
||||
articles3 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Order("id DESC").
|
||||
Page(1, 3).
|
||||
Select("id,title,author")
|
||||
test3["result"] = len(articles3) <= 3
|
||||
test3["count"] = len(articles3)
|
||||
tests = append(tests, test3)
|
||||
|
||||
// 6.4 Offset 偏移
|
||||
test4 := Map{"name": "Offset 偏移查询"}
|
||||
articles4 := that.Db.Table("article").
|
||||
Where("state", 0).
|
||||
Limit(3).
|
||||
Offset(2).
|
||||
Select("id,title")
|
||||
test4["result"] = len(articles4) <= 3
|
||||
test4["count"] = len(articles4)
|
||||
test4["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test4)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 7. 批量插入测试 ====================
|
||||
func testInserts(that *Context) Map {
|
||||
result := Map{"name": "批量插入测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 7.1 批量插入
|
||||
test1 := Map{"name": "Inserts 批量插入"}
|
||||
timestamp := time.Now().UnixNano()
|
||||
affected1 := that.Db.Inserts("test_batch", []Map{
|
||||
{"name": fmt.Sprintf("批量测试1_%d", timestamp), "title": "标题1", "state": 1},
|
||||
{"name": fmt.Sprintf("批量测试2_%d", timestamp), "title": "标题2", "state": 1},
|
||||
{"name": fmt.Sprintf("批量测试3_%d", timestamp), "title": "标题3", "state": 1},
|
||||
})
|
||||
test1["result"] = affected1 >= 0
|
||||
test1["affected"] = affected1
|
||||
test1["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 7.2 带 [#] 的批量插入
|
||||
test2 := Map{"name": "Inserts 带 [#] 标记"}
|
||||
timestamp2 := time.Now().UnixNano()
|
||||
affected2 := that.Db.Inserts("test_batch", []Map{
|
||||
{"name": fmt.Sprintf("带时间测试1_%d", timestamp2), "title": "标题带时间1", "state": 1, "create_time[#]": "NOW()"},
|
||||
{"name": fmt.Sprintf("带时间测试2_%d", timestamp2), "title": "标题带时间2", "state": 1, "create_time[#]": "NOW()"},
|
||||
})
|
||||
test2["result"] = affected2 >= 0
|
||||
test2["affected"] = affected2
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 清理测试数据
|
||||
that.Db.Delete("test_batch", Map{"name[~]": fmt.Sprintf("_%d", timestamp)})
|
||||
that.Db.Delete("test_batch", Map{"name[~]": fmt.Sprintf("_%d", timestamp2)})
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 8. Upsert 测试 ====================
|
||||
func testUpsert(that *Context) Map {
|
||||
result := Map{"name": "Upsert测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 使用 admin 表测试 Upsert(MySQL ON DUPLICATE KEY UPDATE)
|
||||
timestamp := time.Now().Unix()
|
||||
testPhone := fmt.Sprintf("199%08d", timestamp%100000000)
|
||||
|
||||
// 8.1 Upsert 插入新记录
|
||||
test1 := Map{"name": "Upsert 插入新记录 (admin表)"}
|
||||
affected1 := that.Db.Upsert("admin",
|
||||
Map{
|
||||
"name": "Upsert测试管理员",
|
||||
"phone": testPhone,
|
||||
"state": 1,
|
||||
"password": "test123",
|
||||
"role_id": 1,
|
||||
"title": "测试职位",
|
||||
"create_time[#]": "NOW()",
|
||||
"modify_time[#]": "NOW()",
|
||||
},
|
||||
Slice{"phone"},
|
||||
Slice{"name", "state", "title", "modify_time"},
|
||||
)
|
||||
test1["result"] = affected1 >= 0
|
||||
test1["affected"] = affected1
|
||||
test1["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 8.2 Upsert 更新已存在记录
|
||||
test2 := Map{"name": "Upsert 更新已存在记录"}
|
||||
affected2 := that.Db.Upsert("admin",
|
||||
Map{
|
||||
"name": "Upsert更新后管理员",
|
||||
"phone": testPhone,
|
||||
"state": 1,
|
||||
"password": "updated123",
|
||||
"role_id": 2,
|
||||
"title": "更新后职位",
|
||||
"create_time[#]": "NOW()",
|
||||
"modify_time[#]": "NOW()",
|
||||
},
|
||||
Slice{"phone"},
|
||||
Slice{"name", "title", "role_id", "modify_time"},
|
||||
)
|
||||
test2["result"] = affected2 >= 0
|
||||
test2["affected"] = affected2
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 验证更新结果
|
||||
updatedAdmin := that.Db.Get("admin", "*", Map{"phone": testPhone})
|
||||
test2["updatedAdmin"] = updatedAdmin
|
||||
|
||||
// 清理测试数据
|
||||
that.Db.Delete("admin", Map{"phone": testPhone})
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 9. 事务测试 ====================
|
||||
func testTransaction(that *Context) Map {
|
||||
result := Map{"name": "事务测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 9.1 事务成功提交
|
||||
test1 := Map{"name": "事务成功提交"}
|
||||
timestamp := time.Now().Unix()
|
||||
testName1 := fmt.Sprintf("事务测试_%d", timestamp)
|
||||
|
||||
success1 := that.Db.Action(func(tx HoTimeDB) bool {
|
||||
// 插入记录到 test_batch 表
|
||||
recordId := tx.Insert("test_batch", Map{
|
||||
"name": testName1,
|
||||
"title": "事务提交测试",
|
||||
"state": 1,
|
||||
"create_time[#]": "NOW()",
|
||||
})
|
||||
|
||||
return recordId != 0
|
||||
})
|
||||
|
||||
test1["result"] = success1
|
||||
// 验证数据是否存在
|
||||
checkRecord := that.Db.Get("test_batch", "*", Map{"name": testName1})
|
||||
test1["recordExists"] = checkRecord != nil
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 9.2 事务回滚
|
||||
test2 := Map{"name": "事务回滚"}
|
||||
testName2 := fmt.Sprintf("事务回滚测试_%d", timestamp)
|
||||
|
||||
success2 := that.Db.Action(func(tx HoTimeDB) bool {
|
||||
// 插入记录
|
||||
_ = tx.Insert("test_batch", Map{
|
||||
"name": testName2,
|
||||
"title": "事务回滚测试",
|
||||
"state": 1,
|
||||
"create_time[#]": "NOW()",
|
||||
})
|
||||
|
||||
// 返回 false 触发回滚
|
||||
return false
|
||||
})
|
||||
|
||||
test2["result"] = !success2 // 期望回滚,所以 success2 应该为 false
|
||||
// 验证数据是否不存在(已回滚)
|
||||
checkRecord2 := that.Db.Get("test_batch", "*", Map{"name": testName2})
|
||||
test2["recordRolledBack"] = checkRecord2 == nil
|
||||
tests = append(tests, test2)
|
||||
|
||||
// 清理测试数据
|
||||
that.Db.Delete("test_batch", Map{"name": testName1})
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 10. 原生 SQL 测试 ====================
|
||||
func testRawSQL(that *Context) Map {
|
||||
result := Map{"name": "原生SQL测试", "tests": Slice{}}
|
||||
tests := Slice{}
|
||||
|
||||
// 10.1 Query 查询 - 使用真实的 article 表
|
||||
test1 := Map{"name": "Query 原生查询"}
|
||||
articles1 := that.Db.Query("SELECT id, title, author FROM `article` WHERE state = ? LIMIT ?", 0, 5)
|
||||
test1["result"] = len(articles1) >= 0
|
||||
test1["count"] = len(articles1)
|
||||
tests = append(tests, test1)
|
||||
|
||||
// 10.2 Exec 执行 - 使用 article 表
|
||||
test2 := Map{"name": "Exec 原生执行"}
|
||||
// 更新一条记录
|
||||
testArticle := that.Db.Get("article", "id", Map{"state": 0})
|
||||
if testArticle != nil {
|
||||
res, err := that.Db.Exec("UPDATE `article` SET modify_time = NOW() WHERE id = ?", testArticle.GetInt64("id"))
|
||||
if err.GetError() == nil && res != nil {
|
||||
affected, _ := res.RowsAffected()
|
||||
test2["result"] = affected >= 0
|
||||
test2["affected"] = affected
|
||||
} else {
|
||||
test2["result"] = false
|
||||
test2["error"] = err.GetError()
|
||||
}
|
||||
} else {
|
||||
test2["result"] = true
|
||||
test2["note"] = "无可用测试数据"
|
||||
}
|
||||
tests = append(tests, test2)
|
||||
|
||||
// ==================== IN/NOT IN 数组测试 ====================
|
||||
// H1: IN (?) 配合非空数组能正确展开
|
||||
test3 := Map{"name": "H1: IN (?) 非空数组展开"}
|
||||
articles3 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{1, 2, 3, 4, 5})
|
||||
test3["result"] = len(articles3) >= 0
|
||||
test3["count"] = len(articles3)
|
||||
test3["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test3)
|
||||
|
||||
// H2: IN (?) 配合空数组替换为 1=0
|
||||
test4 := Map{"name": "H2: IN (?) 空数组替换为1=0"}
|
||||
articles4 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{})
|
||||
test4["result"] = len(articles4) == 0 // 空数组的IN应该返回0条
|
||||
test4["count"] = len(articles4)
|
||||
test4["lastQuery"] = that.Db.LastQuery
|
||||
test4["expected"] = "count=0, SQL应包含1=0"
|
||||
tests = append(tests, test4)
|
||||
|
||||
// H3: NOT IN (?) 配合空数组替换为 1=1
|
||||
test5 := Map{"name": "H3: NOT IN (?) 空数组替换为1=1"}
|
||||
articles5 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{})
|
||||
test5["result"] = len(articles5) > 0 // NOT IN空数组应该返回记录
|
||||
test5["count"] = len(articles5)
|
||||
test5["lastQuery"] = that.Db.LastQuery
|
||||
test5["expected"] = "count>0, SQL应包含1=1"
|
||||
tests = append(tests, test5)
|
||||
|
||||
// H4: NOT IN (?) 配合非空数组正常展开
|
||||
test6 := Map{"name": "H4: NOT IN (?) 非空数组展开"}
|
||||
articles6 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{1, 2, 3})
|
||||
test6["result"] = len(articles6) >= 0
|
||||
test6["count"] = len(articles6)
|
||||
test6["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test6)
|
||||
|
||||
// H5: 普通 ? 占位符保持原有行为
|
||||
test7 := Map{"name": "H5: 普通?占位符不受影响"}
|
||||
articles7 := that.Db.Query("SELECT id, title FROM `article` WHERE state = ? LIMIT ?", 0, 5)
|
||||
test7["result"] = len(articles7) >= 0
|
||||
test7["count"] = len(articles7)
|
||||
test7["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test7)
|
||||
|
||||
// 额外测试: Select ORM方法的空数组处理
|
||||
test8 := Map{"name": "ORM Select: IN空数组"}
|
||||
articles8 := that.Db.Select("article", "id,title", Map{"id": []int{}, "LIMIT": 10})
|
||||
test8["result"] = len(articles8) == 0
|
||||
test8["count"] = len(articles8)
|
||||
test8["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test8)
|
||||
|
||||
// 额外测试: Select ORM方法的NOT IN空数组处理
|
||||
test9 := Map{"name": "ORM Select: NOT IN空数组"}
|
||||
articles9 := that.Db.Select("article", "id,title", Map{"id[!]": []int{}, "LIMIT": 10})
|
||||
test9["result"] = len(articles9) > 0 // NOT IN 空数组应返回记录
|
||||
test9["count"] = len(articles9)
|
||||
test9["lastQuery"] = that.Db.LastQuery
|
||||
tests = append(tests, test9)
|
||||
|
||||
result["tests"] = tests
|
||||
result["success"] = true
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/aliyun"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var CompanyCtr = Ctr{
|
||||
|
||||
"search": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords == "" {
|
||||
keywords = that.Req.FormValue("company_name")
|
||||
}
|
||||
if keywords == "" {
|
||||
keywords = that.Req.FormValue("name")
|
||||
}
|
||||
|
||||
if len(keywords) < 2 {
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
|
||||
res, err := aliyun.Company.GetCompanyList(keywords)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
if res.GetCeilInt64("status") != 200 {
|
||||
fmt.Println(err)
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, res.GetMap("data").GetSlice("list"))
|
||||
},
|
||||
"search_info": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
name := that.Req.FormValue("name")
|
||||
|
||||
if len(name) < 2 {
|
||||
that.Display(3, "找不到企业")
|
||||
return
|
||||
}
|
||||
|
||||
res, err := aliyun.Company.GetCompanyBaseInfo(name)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
that.Display(4, "查询失败")
|
||||
return
|
||||
}
|
||||
if res.GetBool("status") != true {
|
||||
fmt.Println(err)
|
||||
that.Display(4, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, res.GetMap("data"))
|
||||
},
|
||||
|
||||
"info": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("company", "*", Map{"id": id})
|
||||
if res == nil {
|
||||
that.Display(4, "找不到企业")
|
||||
return
|
||||
}
|
||||
//先不做限制
|
||||
//if res.GetCeilInt("salesman_id")!=that.Session("salesman_id").ToCeilInt(){
|
||||
// that.Display(4,"不是你的企业")
|
||||
// return
|
||||
//}
|
||||
|
||||
res["technology_center_flag"] = strToArray(res.GetString("technology_center_flag"))
|
||||
res["engineering_center_flag"] = strToArray(res.GetString("engineering_center_flag"))
|
||||
res["engineering_laboratory_flag"] = strToArray(res.GetString("engineering_laboratory_flag"))
|
||||
res["key_laboratory_flag"] = strToArray(res.GetString("key_laboratory_flag"))
|
||||
res["industrial_design_center_flag"] = strToArray(res.GetString("industrial_design_center_flag"))
|
||||
res["high_level_talents_flag1"] = strToArray(res.GetString("high_level_talents_flag1"))
|
||||
res["tags"] = strToArray(res.GetString("tags"))
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
"edit": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
company := that.Db.Get("company", "*", Map{"id": id})
|
||||
delete(company, "id")
|
||||
delete(company, "salesman_id")
|
||||
delete(company, "provider_id")
|
||||
delete(company, "user_id")
|
||||
delete(company, "del_flag")
|
||||
delete(company, "state")
|
||||
delete(company, "create_time")
|
||||
delete(company, "modify_time")
|
||||
data := Map{}
|
||||
for k, _ := range company {
|
||||
if that.Req.Form[k] != nil {
|
||||
if k == "tags" || k == "high_level_talents_flag1" || k == "technology_center_flag" || k == "engineering_center_flag" || k == "engineering_laboratory_flag" || k == "key_laboratory_flag" || k == "industrial_design_center_flag" {
|
||||
data[k] = arrayToStr(ObjToSlice(that.Req.Form[k]))
|
||||
} else {
|
||||
data[k] = that.Req.FormValue(k)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
that.Db.Update("company", data, Map{"id": id})
|
||||
|
||||
that.Display(0, "更新成功")
|
||||
|
||||
},
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var DeclareCtr = Ctr{
|
||||
|
||||
"info": func(that *Context) {
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("declare", "*", Map{"id": id})
|
||||
|
||||
if res == nil {
|
||||
that.Display(4, "找不到通知公告")
|
||||
return
|
||||
}
|
||||
res["click_num"] = res.GetCeilInt64("click_num") + res.GetCeilInt64("click_num_base") + 1
|
||||
delete(res, "click_num_base")
|
||||
|
||||
res["favorite_num"] = res.GetCeilInt64("favorite_num") + res.GetCeilInt64("favorite_num_base")
|
||||
delete(res, "favorite_num_base")
|
||||
|
||||
article := that.Db.Get("article", "*", Map{"id": res.GetCeilInt64("article_id")})
|
||||
if article != nil {
|
||||
article["click_num"] = article.GetCeilInt64("click_num") + article.GetCeilInt64("click_num_base") + 1
|
||||
delete(article, "click_num_base")
|
||||
|
||||
article["favorite_num"] = article.GetCeilInt64("favorite_num") + article.GetCeilInt64("favorite_num_base")
|
||||
delete(article, "favorite_num_base")
|
||||
}
|
||||
|
||||
res["article"] = article
|
||||
|
||||
//浏览量加1
|
||||
that.Db.Update("declare", Map{"click_num[#]": "click_num+1"}, Map{"id": id})
|
||||
//浏览量加1
|
||||
that.Db.Update("article", Map{"click_num[#]": "click_num+1"}, Map{"id": res.GetCeilInt64("article_id")})
|
||||
|
||||
//查询是否已关注
|
||||
if that.Session("user_id").Data != nil {
|
||||
favorite := that.Db.Get("favorite", "user_id,declare_id", Map{"AND": Map{"declare_id": id, "user_id": that.Session("user_id").ToCeilInt(), "del_flag": 0}})
|
||||
res["favorite"] = favorite
|
||||
}
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "declare_id[!]": nil}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "content[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["release_date[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["release_date[<=]"] = finishTime
|
||||
}
|
||||
|
||||
dispatchName := that.Req.FormValue("dispatch_name")
|
||||
if dispatchName != "" {
|
||||
data["dispatch_name"] = dispatchName
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "release_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("article", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("article", "id,title,description,department_id,click_num+click_num_base AS click_num,"+
|
||||
"favorite_num_base+favorite_num AS favorite_num,dispatch_num,dispatch_name,prepare_date,release_time,expire_date,area_id,status,declare_id,declare_id,declare_id,dispatch_name,policy_level", data)
|
||||
for _, v := range res {
|
||||
|
||||
//if v.GetCeilInt("declare_id")>0{
|
||||
// v["declare"]=that.Db.Get("declare","id,tag",Map{"id":v.GetCeilInt("declare_id")})
|
||||
//}
|
||||
//if v.GetCeilInt("declare_id")>0{
|
||||
// v["declare"]=that.Db.Get("declare","tag",Map{"id":v.GetCeilInt("declare_id")})
|
||||
//}
|
||||
if v.GetCeilInt("declare_id") > 0 {
|
||||
v["declare"] = that.Db.Get("declare", "money_scope_min,money_scope_max,status", Map{"id": v.GetCeilInt("declare_id")})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Project 管理端项目
|
||||
var Project = Proj{
|
||||
"company": CompanyCtr,
|
||||
"declare": DeclareCtr,
|
||||
"matters": MattersCtr,
|
||||
"order": OrderCtr,
|
||||
"salesman": Salesman,
|
||||
"sms": Sms,
|
||||
"tag": TagCtr,
|
||||
"user": UserCtr,
|
||||
"wechat": Wechat,
|
||||
}
|
||||
|
||||
func strToArray(str string) Slice {
|
||||
s := Slice{}
|
||||
|
||||
olds := strings.Split(str, ",")
|
||||
for _, v := range olds {
|
||||
if v != "" {
|
||||
s = append(s, v)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func arrayToStr(ars Slice) string {
|
||||
s := ","
|
||||
if ars == nil {
|
||||
return s
|
||||
}
|
||||
for k, _ := range ars {
|
||||
s = s + ars.GetString(k) + ","
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//生成随机码的6位md5
|
||||
func getSn() string {
|
||||
x := Rand(8)
|
||||
return Substr(Md5(ObjToStr(int64(x)+time.Now().UnixNano()+int64(Rand(6)))), 0, 6)
|
||||
}
|
||||
|
||||
//生成随机码的4位随机数
|
||||
func getCode() string {
|
||||
//res := ""
|
||||
//for i := 0; i < 4; i++ {
|
||||
res := ObjToStr(RandX(1000, 9999))
|
||||
//}
|
||||
return res
|
||||
}
|
||||
|
||||
// 过滤 emoji 表情
|
||||
func FilterEmoji(content string) string {
|
||||
new_content := ""
|
||||
for _, value := range content {
|
||||
_, size := utf8.DecodeRuneInString(string(value))
|
||||
if size <= 3 {
|
||||
new_content += string(value)
|
||||
}
|
||||
}
|
||||
return new_content
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var MattersCtr = Ctr{
|
||||
|
||||
"info": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("matters", "*", Map{"id": id})
|
||||
if res == nil {
|
||||
that.Display(4, "找不到事项")
|
||||
return
|
||||
}
|
||||
if res.GetCeilInt("salesman_id") != that.Session("salesman_id").ToCeilInt() {
|
||||
that.Display(4, "不是你的事项")
|
||||
return
|
||||
}
|
||||
|
||||
res["user"] = that.Db.Get("user", "id,name,nickname,company_id", Map{"id": res.GetCeilInt64("user_id")})
|
||||
if res.GetMap("user") != nil && res.GetMap("user").GetCeilInt64("company_id") != 0 {
|
||||
res["company"] = that.Db.Get("company", "id,name", Map{"id": res.GetMap("user").GetCeilInt64("company_id")})
|
||||
}
|
||||
that.Display(0, res)
|
||||
},
|
||||
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
userId := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
tp := that.Req.FormValue("type")
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "salesman_id": that.Session("salesman_id").Data}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "content[~]": keywords}
|
||||
}
|
||||
|
||||
if userId != 0 {
|
||||
|
||||
user := that.Db.Get("user", "id", Map{"AND": Map{"id": userId, "salesman_id": that.Session("salesman_id").Data}})
|
||||
if user != nil {
|
||||
data["user_id"] = userId
|
||||
}
|
||||
}
|
||||
|
||||
if tp != "" {
|
||||
data["type"] = ObjToInt("tp")
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modify_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modify_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data}
|
||||
}
|
||||
|
||||
count := that.Db.Count("matters", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("matters", "*", data)
|
||||
|
||||
for _, v := range res {
|
||||
if v.GetCeilInt64("user_id") != 0 {
|
||||
v["user"] = that.Db.Get("user", "id,avatar,name,nickname,company_id", Map{"id": v.GetCeilInt64("user_id")})
|
||||
if v.GetMap("user") != nil && v.GetMap("user").GetCeilInt64("company_id") != 0 {
|
||||
v["company"] = that.Db.Get("company", "id,name", Map{"id": v.GetMap("user").GetCeilInt64("company_id")})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var OrderCtr = Ctr{
|
||||
"info": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
salesman := that.Db.Get("salesman", "*", Map{"id": that.Session("salesman_id").Data})
|
||||
if salesman == nil {
|
||||
that.Display(2, "登录错误")
|
||||
return
|
||||
}
|
||||
|
||||
res := that.Db.Get("order", "*", Map{"AND": Map{"id": id, "provider_id": salesman.GetCeilInt64("provider_id")}})
|
||||
|
||||
if res == nil {
|
||||
that.Display(4, "找不到对应订单")
|
||||
return
|
||||
}
|
||||
|
||||
//if res.GetCeilInt("salesman_id") != that.Session("salesman_id").ToCeilInt() {
|
||||
// that.Display(4, "不是你的订单")
|
||||
// return
|
||||
//}
|
||||
|
||||
if res.GetCeilInt("user_id") > 0 {
|
||||
res["user"] = that.Db.Get("user", "name,nickname,avatar", Map{"id": res.GetCeilInt("user_id")})
|
||||
}
|
||||
|
||||
res["order_record"] = that.Db.Select("order_record", "remarks,modify_time", Map{"order_id": res.GetCeilInt("id"), "ORDER": "modify_time DESC"})
|
||||
|
||||
that.Display(0, res)
|
||||
},
|
||||
"create_order_record": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
remarks := that.Req.FormValue("remarks")
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"id": that.Session("salesman_id").Data})
|
||||
if salesman == nil {
|
||||
that.Display(2, "登录错误")
|
||||
return
|
||||
}
|
||||
|
||||
order := that.Db.Get("order", "*", Map{"AND": Map{"id": id, "provider_id": salesman.GetCeilInt64("provider_id")}})
|
||||
if order == nil {
|
||||
that.Display(4, "不是属于你的订单")
|
||||
return
|
||||
}
|
||||
|
||||
if order.GetCeilInt("status") != 0 {
|
||||
that.Display(4, "已完结订单,不可操作")
|
||||
return
|
||||
}
|
||||
|
||||
orderRecordId := that.Db.Insert("order_record", Map{
|
||||
"order_id": id,
|
||||
"user_id": order.GetCeilInt64("user_id"),
|
||||
"remarks": salesman.GetString("name") + ":" + remarks,
|
||||
"salesman_id": order.GetCeilInt64("salesman_id"),
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
|
||||
that.Db.Update("order", Map{"order_record_id": orderRecordId}, Map{"id": id})
|
||||
|
||||
that.Display(0, "新增订单记录成功")
|
||||
|
||||
},
|
||||
"edit": func(that *Context) {
|
||||
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
id := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if id == 0 {
|
||||
that.Display(3, "请求参数异常")
|
||||
return
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"id": that.Session("salesman_id").Data})
|
||||
if salesman == nil {
|
||||
that.Display(2, "登录错误")
|
||||
return
|
||||
}
|
||||
|
||||
policyDeclareFlag := ObjToInt(that.Req.FormValue("policy_declare_flag"))
|
||||
declareId := ObjToInt(that.Req.FormValue("declare_id"))
|
||||
|
||||
intellectualPropertyFlag := ObjToInt(that.Req.FormValue("intellectual_property_flag"))
|
||||
intellectualPropertyCount := ObjToInt(that.Req.FormValue("intellectual_property_count"))
|
||||
taxOnsultingFlag := ObjToInt(that.Req.FormValue("tax_onsulting_flag"))
|
||||
|
||||
lawFlag := ObjToInt(that.Req.FormValue("law_flag"))
|
||||
status := ObjToInt(that.Req.FormValue("status"))
|
||||
|
||||
data := Map{
|
||||
"policy_declare_flag": policyDeclareFlag,
|
||||
"intellectual_property_flag": intellectualPropertyFlag,
|
||||
"intellectual_property_count": intellectualPropertyCount,
|
||||
"tax_onsulting_flag": taxOnsultingFlag,
|
||||
"law_flag": lawFlag,
|
||||
"status": status,
|
||||
"modify_time[#]": "now()",
|
||||
}
|
||||
|
||||
if declareId != 0 {
|
||||
data["declare_id"] = declareId
|
||||
}
|
||||
|
||||
order := that.Db.Get("order", "*", Map{"AND": Map{"id": id, "provider_id": salesman.GetCeilInt64("provider_id")}})
|
||||
if order == nil {
|
||||
that.Display(4, "不是属于你的订单")
|
||||
return
|
||||
}
|
||||
|
||||
if order.GetCeilInt("status") != 0 {
|
||||
that.Display(4, "已完结订单,不可操作")
|
||||
return
|
||||
}
|
||||
|
||||
data["order_record_id"] = that.Db.Insert("order_record", Map{
|
||||
"order_id": id,
|
||||
"user_id": order.GetCeilInt64("user_id"),
|
||||
"remarks": salesman.GetString("name") + "变更了订单服务内容",
|
||||
"salesman_id": order.GetCeilInt64("salesman_id"),
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
})
|
||||
re := that.Db.Update("order", data, Map{"id": id})
|
||||
if re == 0 {
|
||||
that.Display(4, "变更订单内容失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, "变更订单内容成功")
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
userId := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"id": that.Session("salesman_id").Data})
|
||||
if salesman == nil {
|
||||
that.Display(2, "登录错误")
|
||||
return
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "provider_id": salesman.GetCeilInt64("provider_id")}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"sn[~]": keywords, "name[~]": keywords}
|
||||
}
|
||||
|
||||
if userId != 0 {
|
||||
|
||||
user := that.Db.Get("user", "id", Map{"AND": Map{"id": userId, "salesman_id": that.Session("salesman_id").Data}})
|
||||
if user != nil {
|
||||
data["user_id"] = userId
|
||||
}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modify_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modify_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data}
|
||||
}
|
||||
|
||||
count := that.Db.Count("order", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("order", "*", data)
|
||||
for _, v := range res {
|
||||
|
||||
//if v.GetCeilInt("policy_id")>0{
|
||||
// v["policy"]=that.Db.Get("policy","id,tag",Map{"id":v.GetCeilInt("policy_id")})
|
||||
//}
|
||||
if v.GetCeilInt("user_id") > 0 {
|
||||
v["user"] = that.Db.Get("user", "name,nickname,avatar", Map{"id": v.GetCeilInt("user_id")})
|
||||
}
|
||||
if v.GetCeilInt("order_record_id") > 0 {
|
||||
v["order_record"] = that.Db.Get("order_record", "remarks,modify_time", Map{"id": v.GetCeilInt("order_record_id")})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var Salesman = Ctr{
|
||||
"test": func(that *Context) {
|
||||
that.Session("salesman_id", 1)
|
||||
that.Session("wechat_id", 1)
|
||||
that.Display(0, 1)
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"id": that.Session("salesman_id").Data})
|
||||
if salesman == nil {
|
||||
that.Display(4, "找不到该业务员")
|
||||
return
|
||||
}
|
||||
|
||||
if salesman.GetString("nickname") == "" {
|
||||
wechat := that.Db.Get("wechat", "*", Map{"salesman_id": salesman.GetCeilInt64("id")})
|
||||
if wechat != nil {
|
||||
salesman["nickname"] = wechat.GetString("nickname")
|
||||
salesman["avatar"] = wechat.GetString("avatar")
|
||||
that.Db.Update("salesman", Map{"nickname": wechat.GetString("nickname"), "avatar": wechat.GetString("avatar")}, Map{"id": salesman.GetCeilInt64("id")})
|
||||
}
|
||||
}
|
||||
salesman["user"] = that.Db.Count("user", Map{"AND": Map{"salesman_id": that.Session("salesman_id").Data, "del_flag": 0}})
|
||||
salesman["matters"] = that.Db.Count("matters", Map{"AND": Map{"salesman_id": that.Session("salesman_id").Data, "del_flag": 0}})
|
||||
|
||||
salesman["admin"] = that.Db.Get("admin", "id,name,avatar", Map{"id": salesman.GetCeilInt64("admin_id")})
|
||||
salesman["provider"] = that.Db.Get("provider", "*", Map{"id": salesman.GetCeilInt64("provider_id")})
|
||||
if salesman["provider"] != nil {
|
||||
salesman["provider_salesman"] = that.Db.Get("salesman", "id,nickname,name,avatar", Map{"id": salesman.GetMap("provider").GetCeilInt64("salesman_id")})
|
||||
}
|
||||
|
||||
that.Display(0, salesman)
|
||||
},
|
||||
|
||||
"search": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"id": that.Session("salesman_id").Data})
|
||||
if salesman == nil {
|
||||
that.Display(4, "找不到该业务员")
|
||||
return
|
||||
}
|
||||
|
||||
provider := that.Db.Get("provider", "*", Map{"id": salesman.GetCeilInt64("provider_id")})
|
||||
|
||||
if provider.GetCeilInt("salesman_id") != salesman.GetCeilInt("id") {
|
||||
that.Display(0, Slice{})
|
||||
return
|
||||
}
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "provider_id": salesman.GetCeilInt64("provider_id")}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"name[~]": keywords, "nickname[~]": keywords, "phone[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modifye_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modifye_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
count := that.Db.Count("salesman", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("salesman", "id,name,nickname,avatar", data)
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"login": func(that *Context) {
|
||||
|
||||
code := that.Req.FormValue("code")
|
||||
phone := that.Req.FormValue("phone")
|
||||
|
||||
if phone != that.Session("phone").ToStr() && code == that.Session("code").ToStr() {
|
||||
that.Display(3, "手机号或者验证码错误")
|
||||
return
|
||||
}
|
||||
|
||||
if that.Session("wechat_id").Data == nil {
|
||||
that.Display(2, "还未登录")
|
||||
return
|
||||
}
|
||||
|
||||
wechat := that.Db.Get("wechat", "*", Map{"id": that.Session("wechat_id").ToCeilInt()})
|
||||
|
||||
salesman := that.Db.Get("salesman", "*", Map{"phone": phone})
|
||||
|
||||
if salesman == nil {
|
||||
that.Display(3, "找不到企服商")
|
||||
return
|
||||
}
|
||||
|
||||
if wechat == nil {
|
||||
that.Display(2, "还未绑定微信")
|
||||
return
|
||||
}
|
||||
|
||||
//有用户直接返回
|
||||
if wechat.GetCeilInt("salesman_id") != 0 && wechat.GetCeilInt64("salesman_id") != salesman.GetCeilInt64("id") {
|
||||
that.Display(5, "你已经绑定了其他商户")
|
||||
return
|
||||
}
|
||||
|
||||
that.Db.Update("wechat", Map{"salesman_id": salesman.GetCeilInt64("id")}, Map{"id": wechat.GetCeilInt64("id")})
|
||||
that.Db.Update("salesman", Map{"login_time[#]": "now()", "modify_time[#]": "now()"},
|
||||
Map{"id": salesman.GetCeilInt("id")})
|
||||
|
||||
wechat["salesman_id"] = salesman.GetCeilInt64("id")
|
||||
that.Session("salesman_id", salesman.GetCeilInt64("id"))
|
||||
that.Display(0, "登录成功!")
|
||||
|
||||
},
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/ddsms"
|
||||
)
|
||||
|
||||
var Sms = Ctr{
|
||||
//只允许微信验证过的或者登录成功的发送短信
|
||||
"send": func(that *Context) {
|
||||
|
||||
phone := that.Req.FormValue("phone")
|
||||
if len(phone) < 11 {
|
||||
that.Display(3, "手机号格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
if that.Session("wechat_id").Data == nil && that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录不可发送短信")
|
||||
return
|
||||
}
|
||||
|
||||
salesman := that.Db.Get("salesman", "id", common.Map{"phone": phone})
|
||||
if salesman == nil {
|
||||
that.Display(3, "你还不是企服人员,请联系管理员")
|
||||
return
|
||||
}
|
||||
|
||||
code := getCode()
|
||||
that.Session("phone", phone)
|
||||
that.Session("code", code)
|
||||
|
||||
ddsms.DDY.SendYZM(phone, that.Config.GetString("smsLogin"), map[string]string{"code": code})
|
||||
|
||||
that.Display(0, "发送成功")
|
||||
},
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var TagCtr = Ctr{
|
||||
|
||||
"create": func(that *Context) {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
|
||||
oldTag := that.Db.Get("tag", "id", Map{"name": name})
|
||||
if oldTag != nil {
|
||||
that.Display(4, "此标签已存在")
|
||||
return
|
||||
}
|
||||
|
||||
re := that.Db.Insert("tag", Map{
|
||||
"user_id": that.Session("user_id").Data,
|
||||
"name": name,
|
||||
"remark": "用户上传",
|
||||
"create_time[#]": "now()",
|
||||
"modify_time[#]": "now()",
|
||||
"state": 1, //先置为异常状态,等待审核通过
|
||||
"del_flag": 0,
|
||||
})
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "添加失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, "添加成功")
|
||||
return
|
||||
|
||||
},
|
||||
//用户微信公众号或者小程序登录
|
||||
"search": func(that *Context) {
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 40
|
||||
}
|
||||
|
||||
data := Map{"del_flag": 0, "state": 0}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"name[~]": keywords, "remark[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modify_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modify_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data}
|
||||
}
|
||||
|
||||
count := that.Db.Count("tag", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("tag", "id,name,remark", data)
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
var UserCtr = Ctr{
|
||||
//用户微信公众号或者小程序登录
|
||||
"info": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
userId := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if userId == 0 {
|
||||
that.Display(3, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
user := that.Db.Get("user", "*", Map{"id": that.Session("user_id").ToCeilInt()})
|
||||
if user == nil {
|
||||
that.Display(4, "获取个人信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
delete(user, "password")
|
||||
|
||||
that.Display(0, user)
|
||||
},
|
||||
|
||||
"edit": func(that *Context) {
|
||||
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
userId := ObjToInt(that.Req.FormValue("id"))
|
||||
|
||||
if userId == 0 {
|
||||
that.Display(3, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
nickname := that.Req.FormValue("nickname")
|
||||
sex := that.Req.FormValue("sex")
|
||||
email := that.Req.FormValue("email")
|
||||
avatar := that.Req.FormValue("avatar")
|
||||
address := that.Req.FormValue("address")
|
||||
phone := that.Req.FormValue("phone") //如果更换手机号则要有新的短信验证码
|
||||
|
||||
data := Map{"modify_time[#]": "now()"}
|
||||
if name != "" {
|
||||
data["name"] = name
|
||||
}
|
||||
if nickname != "" {
|
||||
data["nickname"] = nickname
|
||||
}
|
||||
if sex != "" {
|
||||
data["sex"] = sex
|
||||
}
|
||||
if email != "" {
|
||||
data["email"] = email
|
||||
}
|
||||
if avatar != "" {
|
||||
data["avatar"] = avatar
|
||||
}
|
||||
if address != "" {
|
||||
data["address"] = address
|
||||
}
|
||||
if phone != "" {
|
||||
|
||||
data["phone"] = phone
|
||||
}
|
||||
|
||||
re := that.Db.Update("user", data, Map{"id": userId})
|
||||
if re == 0 {
|
||||
that.Display(4, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(4, "修改成功")
|
||||
},
|
||||
|
||||
"search": func(that *Context) {
|
||||
if that.Session("salesman_id").Data == nil {
|
||||
that.Display(2, "没有登录")
|
||||
return
|
||||
}
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
tp := ObjToInt(that.Req.FormValue("type")) //0,无操作,1,已扫码未认证,2,已认证,3,VIP会员
|
||||
|
||||
data := Map{"del_flag": 0, "salesman_id": that.Session("salesman_id").Data}
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
if keywords != "" {
|
||||
data["OR"] = Map{"name[~]": keywords, "nickname[~]": keywords, "phone[~]": keywords}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("starttime")
|
||||
finishTime := that.Req.FormValue("finishtime")
|
||||
|
||||
if startTime != "" {
|
||||
data["modifye_time[>=]"] = startTime
|
||||
}
|
||||
if finishTime != "" {
|
||||
data["modifye_time[<=]"] = finishTime
|
||||
}
|
||||
if tp == 1 {
|
||||
data["certification_flag"] = 0
|
||||
}
|
||||
|
||||
if tp == 2 {
|
||||
data["certification_flag"] = 1
|
||||
data["OR"] = Map{"expiration_time": nil, "expiration_time[#]": "now()"}
|
||||
}
|
||||
|
||||
if tp == 3 {
|
||||
data["certification_flag"] = 1
|
||||
data["expiration_time[>]"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
data = Map{"AND": data, "ORDER": "modify_time DESC"}
|
||||
}
|
||||
|
||||
count := that.Db.Count("user", data)
|
||||
|
||||
res := that.Db.Page(page, pageSize).PageSelect("user", "*", data)
|
||||
|
||||
for _, v := range res {
|
||||
delete(v, "password")
|
||||
}
|
||||
|
||||
that.Display(0, Map{"total": count, "data": res})
|
||||
},
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"code.hoteas.com/golang/hotime/dri/wechat"
|
||||
)
|
||||
|
||||
var Wechat = Ctr{
|
||||
|
||||
////微信注册
|
||||
"code": func(that *Context) {
|
||||
|
||||
if that.Req.FormValue("code") == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
appid, resToken, userInfo, err := wechat.H5Program.GetUserInfo(that.Req.FormValue("code"))
|
||||
|
||||
if err != nil {
|
||||
that.Display(4, err)
|
||||
return
|
||||
}
|
||||
//此次获取的微信信息
|
||||
wechatInfo := Map{
|
||||
"openid": userInfo.OpenID,
|
||||
"acttoken": resToken.AccessToken,
|
||||
"retoken": resToken.RefreshToken,
|
||||
"appid": appid,
|
||||
"unionid": userInfo.Unionid,
|
||||
"nickname": FilterEmoji(userInfo.Nickname),
|
||||
"avatar": userInfo.HeadImgURL,
|
||||
//"create_time[#]":"now()",
|
||||
"modify_time[#]": "now()",
|
||||
"del_flag": 0,
|
||||
"type": 1,
|
||||
}
|
||||
|
||||
wechat := that.Db.Get("wechat", "*", Map{"AND": Map{"openid": userInfo.OpenID, "del_flag": 0}})
|
||||
|
||||
if wechat != nil {
|
||||
//有用户直接返回
|
||||
if wechat.GetCeilInt("salesman_id") != 0 {
|
||||
|
||||
that.Db.Update("salesman", Map{"login_time[#]": "now()"},
|
||||
Map{"id": wechat.GetCeilInt("salesman_id")})
|
||||
that.Session("salesman_id", wechat.GetCeilInt("salesman_id"))
|
||||
|
||||
that.Display(0, "登录成功")
|
||||
return
|
||||
}
|
||||
that.Session("wechat_id", wechat.GetCeilInt("id"))
|
||||
that.Display(2, "暂未绑定")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
wechatInfo["create_time[#]"] = "now()"
|
||||
|
||||
wechatInfo["id"] = that.Db.Insert("wechat", wechatInfo)
|
||||
if wechatInfo.GetCeilInt("id") == 0 {
|
||||
that.Display(4, "创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Session("wechat_id", wechatInfo.GetCeilInt("id"))
|
||||
that.Display(2, "暂未绑定")
|
||||
},
|
||||
|
||||
//网页签名
|
||||
"sign": func(that *Context) {
|
||||
|
||||
signUrl := that.Req.FormValue("url")
|
||||
if signUrl == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
cfg1, e := wechat.H5Program.GetSignUrl(signUrl)
|
||||
|
||||
if e != nil {
|
||||
that.Display(4, e)
|
||||
return
|
||||
}
|
||||
|
||||
sign := Map{
|
||||
"appId": cfg1.AppID,
|
||||
"timestamp": cfg1.Timestamp,
|
||||
"nonceStr": cfg1.NonceStr,
|
||||
"signature": cfg1.Signature,
|
||||
}
|
||||
|
||||
that.Display(0, sign)
|
||||
|
||||
},
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-3b5105e6]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-3b5105e6]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-3b5105e6]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}
|
||||
@@ -0,0 +1 @@
|
||||
body[data-v-c08f3364],dd[data-v-c08f3364],dl[data-v-c08f3364],form[data-v-c08f3364],h1[data-v-c08f3364],h2[data-v-c08f3364],h3[data-v-c08f3364],h4[data-v-c08f3364],h5[data-v-c08f3364],h6[data-v-c08f3364],html[data-v-c08f3364],ol[data-v-c08f3364],p[data-v-c08f3364],pre[data-v-c08f3364],tbody[data-v-c08f3364],textarea[data-v-c08f3364],tfoot[data-v-c08f3364],thead[data-v-c08f3364],ul[data-v-c08f3364]{margin:0;font-size:14px;font-family:Microsoft YaHei}dl[data-v-c08f3364],ol[data-v-c08f3364],ul[data-v-c08f3364]{padding:0}li[data-v-c08f3364]{list-style:none}input[data-v-c08f3364]{border:none;outline:none;font-family:Microsoft YaHei;background-color:#fff}a[data-v-c08f3364]{font-family:Microsoft YaHei;text-decoration:none}[data-v-c08f3364]{margin:0;padding:0}.login[data-v-c08f3364]{position:relative;width:100%;height:100%;background-color:#353d56;background-repeat:no-repeat;background-attachment:fixed;background-size:cover}.login-item[data-v-c08f3364]{position:absolute;top:calc(50% - 30vh);left:60%;min-width:388px;width:18vw;max-width:588px;padding:8vh 30px;box-sizing:border-box;background-size:468px 468px;background:hsla(0,0%,100%,.85);border-radius:10px}.login-item .right-content[data-v-c08f3364]{box-sizing:border-box;width:100%}.login-item .right-content .login-title[data-v-c08f3364]{font-size:26px;font-weight:700;color:#4f619b;text-align:center}.errorMsg[data-v-c08f3364]{width:100%;height:34px;line-height:34px;color:red;font-size:14px;overflow:hidden}.login-item .right-content .inputWrap[data-v-c08f3364]{width:90%;height:32px;line-height:32px;color:#646464;font-size:16px;border:1px solid #b4b4b4;margin:0 auto 5%;padding:2% 10px;border-radius:10px}.login-item .right-content .inputWrap.inputFocus[data-v-c08f3364]{border:1px solid #4f619b;box-shadow:0 0 0 3px rgba(91,113,185,.4)}.login-item .right-content .inputWrap input[data-v-c08f3364]{background-color:transparent;color:#646464;display:inline-block;height:100%;width:80%}.login-btn[data-v-c08f3364]{width:97%;height:52px;text-align:center;line-height:52px;font-size:17px;color:#fff;background-color:#4f619b;border-radius:10px;margin:0 auto;margin-top:50px;cursor:pointer;font-weight:800}
|
||||
@@ -0,0 +1 @@
|
||||
.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-51dbed3c]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-51dbed3c]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-51dbed3c]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}
|
||||
@@ -0,0 +1 @@
|
||||
.full-screen-container[data-v-12e6b782]{z-index:10000}.file-upload .el-upload{background:transparent;width:100%;height:auto;text-align:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-upload .el-upload .el-button{margin-right:10px}.el-upload img[data-v-69e31561]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-69e31561]{font-size:40px;margin:30% 31%;display:block}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}
|
||||
@@ -0,0 +1 @@
|
||||
.left-nav-home-bar{background:#2c3759!important;overflow:hidden;text-overflow:ellipsis}.left-nav-home-bar,.left-nav-home-bar i{color:#fff!important}.el-submenu .el-menu-item{height:40px;line-height:40px;width:auto;min-width:60px;padding:0 10px 0 25px!important;text-overflow:ellipsis;overflow:hidden}.el-menu .el-submenu__title{height:46px;line-height:46px;padding-left:10px!important;text-overflow:ellipsis;overflow:hidden}.left-nav-home-bar i{margin-bottom:6px!important}.el-menu-item-group__title{padding:0 0 0 10px}.el-menu--collapse .el-menu-item-group__title,.el-menu--collapse .el-submenu__title{padding-left:20px!important}.el-menu-item i,.el-submenu__title i{margin-top:-4px;vertical-align:middle;margin:-3px 5px 0 0;right:1px}.head-left[data-v-b2941c10],.head-right[data-v-b2941c10]{display:flex;justify-content:center;flex-direction:column}.head-right[data-v-b2941c10]{align-items:flex-end}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-51dbed3c]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-51dbed3c]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-51dbed3c]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}.el-dialog{margin:auto!important;top:50%;transform:translateY(-50%)}.el-dialog-div{height:75vh;overflow:auto}.el-dialog__body{padding-top:15px;padding-bottom:45px}.el-dialog-div .el-tabs__header{position:absolute;left:1px;top:69px;width:calc(90vw - 42px);margin:0 20px;z-index:1}.el-dialog-div .el-tabs__content{padding-top:50px}.el-dialog-div .el-affix--fixed{bottom:20vh}.el-dialog-div .el-affix--fixed .el-form-item{padding:0!important;background:transparent!important}
|
||||
@@ -0,0 +1 @@
|
||||
.full-screen-container[data-v-12e6b782]{z-index:10000}.file-upload .el-upload{background:transparent;width:100%;height:auto;text-align:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-upload .el-upload .el-button{margin-right:10px}.el-upload img[data-v-96cdfb38]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-96cdfb38]{font-size:40px;margin:30% 31%;display:block}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}
|
||||
@@ -0,0 +1 @@
|
||||
.not-show-tab-label .el-tabs__header{display:none}.el-descriptions__body{background:#f0f0f0}.not-show-tab-search{display:none}.el-table__body-wrapper{margin-bottom:4px;padding-bottom:2px}.el-table__body-wrapper::-webkit-scrollbar{width:8px;height:8px}.el-table__body-wrapper::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px hsla(0,0%,93.3%,.3);background-color:#eee}.el-table__body-wrapper::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(145,143,143,.3);background-color:#918f8f}.input-with-select .el-input-group__prepend{background-color:#fff}.daterange-box .select .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.daterange-box .daterange.el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0;vertical-align:bottom}[data-v-e9f58da4] .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#409eff!important;color:#fff}
|
||||
@@ -0,0 +1 @@
|
||||
.left-nav-home-bar{background:#2c3759!important;overflow:hidden;text-overflow:ellipsis}.left-nav-home-bar,.left-nav-home-bar i{color:#fff!important}.el-submenu .el-menu-item{height:40px;line-height:40px;width:auto;min-width:60px;padding:0 10px 0 25px!important;text-overflow:ellipsis;overflow:hidden}.el-menu .el-submenu__title{height:46px;line-height:46px;padding-left:10px!important;text-overflow:ellipsis;overflow:hidden}.left-nav-home-bar i{margin-bottom:6px!important}.el-menu-item-group__title{padding:0 0 0 10px}.el-menu--collapse .el-menu-item-group__title,.el-menu--collapse .el-submenu__title{padding-left:20px!important}.el-menu-item i,.el-submenu__title i{margin-top:-4px;vertical-align:middle;margin:-3px 5px 0 0;right:1px}.head-left[data-v-b2941c10],.head-right[data-v-b2941c10]{display:flex;justify-content:center;flex-direction:column}.head-right[data-v-b2941c10]{align-items:flex-end}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-3b5105e6]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-3b5105e6]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-3b5105e6]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}.el-dialog{margin:auto!important;top:50%;transform:translateY(-50%)}.el-dialog-div{height:75vh;overflow:auto}.el-dialog__body{padding-top:15px;padding-bottom:45px}.el-dialog-div .el-tabs__header{position:absolute;left:1px;top:69px;width:calc(90vw - 42px);margin:0 20px;z-index:1}.el-dialog-div .el-tabs__content{padding-top:50px}.el-dialog-div .el-affix--fixed{bottom:20vh}.el-dialog-div .el-affix--fixed .el-form-item{padding:0!important;background:transparent!important}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.full-screen-container[data-v-12e6b782]{z-index:10000}.not-show-tab-label .el-tabs__header{display:none}.el-descriptions__body{background:#f0f0f0}.not-show-tab-search{display:none}.el-table__body-wrapper{margin-bottom:4px;padding-bottom:2px}.el-table__body-wrapper::-webkit-scrollbar{width:8px;height:8px}.el-table__body-wrapper::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px hsla(0,0%,93.3%,.3);background-color:#eee}.el-table__body-wrapper::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(145,143,143,.3);background-color:#918f8f}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><script src="js/manage.js"></script><script src="https://api.map.baidu.com/api?v=2.0&ak=bF4Y6tQg94hV2vesn2ZIaUIXO4aRxxRk"></script><title></title><style>body{
|
||||
margin: 0px;
|
||||
}</style><link href="css/chunk-0de923e9.c4e8272a.css" rel="prefetch"><link href="css/chunk-1a458102.466135f0.css" rel="prefetch"><link href="css/chunk-291edee4.508cb3c8.css" rel="prefetch"><link href="css/chunk-4aefa5ec.fcc75990.css" rel="prefetch"><link href="css/chunk-4f81d902.91f1ef17.css" rel="prefetch"><link href="css/chunk-856f3c38.9b9508b8.css" rel="prefetch"><link href="css/chunk-e3f8e5a6.7876554f.css" rel="prefetch"><link href="js/chunk-0de923e9.1f0fca7e.js" rel="prefetch"><link href="js/chunk-1a458102.9a54e9ae.js" rel="prefetch"><link href="js/chunk-25ddb50b.55ec750b.js" rel="prefetch"><link href="js/chunk-291edee4.e8dbe8c8.js" rel="prefetch"><link href="js/chunk-4aefa5ec.c237610d.js" rel="prefetch"><link href="js/chunk-4f81d902.c5fdb7dd.js" rel="prefetch"><link href="js/chunk-7ea17297.38d572ef.js" rel="prefetch"><link href="js/chunk-856f3c38.29c2fcc0.js" rel="prefetch"><link href="js/chunk-e3f8e5a6.ff58e6f8.js" rel="prefetch"><link href="js/chunk-e624c1ca.62513cbc.js" rel="prefetch"><link href="css/app.abfb5de2.css" rel="preload" as="style"><link href="js/app.25e49e88.js" rel="preload" as="script"><link href="css/app.abfb5de2.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but hotime doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/app.25e49e88.js"></script></body></html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user