refactor(api): 公共接口改名 open 并重构契约层为 tools/<子功能>/index.go
- api/common/v1 → api/open/v1,路由前缀 /api/common/v1 → /api/open/v1
- 契约层按子功能拆目录:api/open/v1/tools/{uuid,md5,random,time,ip}/index.go
- 控制器同步拆文件:internal/controller/open/{uuid,md5,random,time,ip}.go
- 命名规则(open 优于 common)与 tools 目录规则记入 AGENTS.md
- 冒烟测试:旧路由 404,新路由 5 端点全部通过
This commit is contained in:
parent
1486bb903f
commit
82b1ad2b5c
13
AGENTS.md
13
AGENTS.md
@ -25,13 +25,14 @@ service.xpcool.com/
|
|||||||
├── api/ # HTTP 契约与 Swagger 元数据
|
├── api/ # HTTP 契约与 Swagger 元数据
|
||||||
│ ├── user/v1/ # /api/v1 用户端 API(登录公开,其余走 UserAuth)
|
│ ├── user/v1/ # /api/v1 用户端 API(登录公开,其余走 UserAuth)
|
||||||
│ ├── admin/v1/ # /admin/v1 管理端 API(AdminAuth + X-Permission)
|
│ ├── admin/v1/ # /admin/v1 管理端 API(AdminAuth + X-Permission)
|
||||||
│ └── common/v1/ # /api/common/v1 公共接口(给前端调用,公开无鉴权)
|
│ └── open/v1/ # /api/open/v1 开放接口(给前端调用,公开无鉴权)
|
||||||
|
│ └── tools/ # 工具子功能契约,每子功能一目录:tools/<name>/index.go
|
||||||
├── common/ # 公共可复用模块(不依赖 internal,可独立抽取成库)
|
├── common/ # 公共可复用模块(不依赖 internal,可独立抽取成库)
|
||||||
│ └── tools/ # 工具模块,按子功能分包(见下文)
|
│ └── tools/ # 工具模块,按子功能分包(见下文)
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── cmd/ # 启动引导(路由分组注册在此)
|
│ ├── cmd/ # 启动引导(路由分组注册在此)
|
||||||
│ ├── consts/ # 错误码与常量
|
│ ├── consts/ # 错误码与常量
|
||||||
│ ├── controller/ # API→service 适配层(不写业务;含 common/ 公共接口实现)
|
│ ├── controller/ # API→service 适配层(不写业务;含 open/ 开放接口实现)
|
||||||
│ ├── service/ # 领域用例(业务逻辑直接写这里,不用 logic/)
|
│ ├── service/ # 领域用例(业务逻辑直接写这里,不用 logic/)
|
||||||
│ ├── dao/ # gf gen dao 生成,禁止手改
|
│ ├── dao/ # gf gen dao 生成,禁止手改
|
||||||
│ ├── model/ # entity/ do/ dto/ vo/(entity、do 生成,禁止手改)
|
│ ├── model/ # entity/ do/ dto/ vo/(entity、do 生成,禁止手改)
|
||||||
@ -43,11 +44,13 @@ service.xpcool.com/
|
|||||||
└── utility/ # (预留)跨切面辅助
|
└── utility/ # (预留)跨切面辅助
|
||||||
```
|
```
|
||||||
|
|
||||||
## 公共接口(api/common/v1,前端调用)
|
## 开放接口(api/open/v1,前端调用)与命名规则
|
||||||
|
|
||||||
- 路由前缀 `/api/common/v1`,**公开、无鉴权**(登录接口外的通用能力),实现于 `internal/controller/common`。
|
- **命名决策**:公共接口前缀用 **open**(不用 common)。理由:`common` 语义偏"内部公共代码",`open` 是开放接口业界惯例(支付宝 /open/api 等),更能表达"对外暴露、无鉴权"。同属"公开"语义的备选还有 `public`。
|
||||||
|
- 路由前缀 `/api/open/v1`,**公开、无鉴权**,实现于 `internal/controller/open`。
|
||||||
|
- **tools 子功能目录规则**:`api/open/v1/tools/<子功能名>/index.go` 定义该子功能的 Req/Res 契约(index.go 内 `package <子功能名>`);控制器 `internal/controller/open/<子功能名>.go` 放对应方法(controller 统一 `package open`)。
|
||||||
- 当前端点:`GET/POST /tools/*`(uuid、md5、random、time、ip),底层复用 `common/tools` Go 包。
|
- 当前端点:`GET/POST /tools/*`(uuid、md5、random、time、ip),底层复用 `common/tools` Go 包。
|
||||||
- 新增公共接口:在 `api/common/v1` 加 Req/Res(g.Meta 带 path/method),在 `internal/controller/common` 加方法,`internal/cmd/cmd.go` 的 `/api/common/v1` 分组会自动绑定。
|
- **新增子功能三步**:① `api/open/v1/tools/<name>/index.go` 写 Req/Res(g.Meta 带 path/method);② `internal/controller/open/<name>.go` 加方法;③ 路由自动绑定,无需改 cmd.go。
|
||||||
|
|
||||||
## 公共工具模块 common/tools
|
## 公共工具模块 common/tools
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,15 @@ service.xpcool.com/
|
|||||||
├── api/ # HTTP contracts and Swagger metadata
|
├── api/ # HTTP contracts and Swagger metadata
|
||||||
│ ├── user/v1/ # /api/v1 - client-facing API
|
│ ├── user/v1/ # /api/v1 - client-facing API
|
||||||
│ ├── admin/v1/ # /admin/v1 - administration API
|
│ ├── admin/v1/ # /admin/v1 - administration API
|
||||||
│ └── common/v1/ # /api/common/v1 - public API for frontends (no auth)
|
│ └── open/v1/ # /api/open/v1 - open API for frontends (no auth)
|
||||||
|
│ └── tools/ # one dir per sub-feature: tools/<name>/index.go
|
||||||
├── common/ # public reusable module (no internal/ deps)
|
├── common/ # public reusable module (no internal/ deps)
|
||||||
│ └── tools/ # utility toolbox: md5, cryptox, uuid, random,
|
│ └── tools/ # utility toolbox: md5, cryptox, uuid, random,
|
||||||
│ # timex, convertx, strx, slicex, ip, filex
|
│ # timex, convertx, strx, slicex, ip, filex
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── cmd/ # application bootstrap and route isolation
|
│ ├── cmd/ # application bootstrap and route isolation
|
||||||
│ ├── consts/ # application error codes and constants
|
│ ├── consts/ # application error codes and constants
|
||||||
│ ├── controller/ # API-to-service adapters only (incl. common/ public API)
|
│ ├── controller/ # API-to-service adapters only (incl. open/ open API)
|
||||||
│ ├── service/ # domain use cases and provider interfaces
|
│ ├── service/ # domain use cases and provider interfaces
|
||||||
│ ├── dao/ # generated by gf gen dao; never hand edited
|
│ ├── dao/ # generated by gf gen dao; never hand edited
|
||||||
│ ├── model/
|
│ ├── model/
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
// Package v1 defines the public common API contracts served at /api/common/v1.
|
|
||||||
//
|
|
||||||
// These endpoints are open to any frontend client (mini/h5/app) and require
|
|
||||||
// no authentication. Implementations live in internal/controller/common and
|
|
||||||
// reuse the common/tools Go packages as their backend.
|
|
||||||
package v1
|
|
||||||
|
|
||||||
import "github.com/gogf/gf/v2/frame/g"
|
|
||||||
|
|
||||||
// UUIDReq generates a unique ID.
|
|
||||||
type UUIDReq struct {
|
|
||||||
g.Meta `path:"/tools/uuid" method:"get" tags:"Common/Tools" summary:"Generate a unique ID"`
|
|
||||||
Short bool `json:"short"` // true: 8-char short code; false: 32-char ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUIDRes is the response of UUIDReq.
|
|
||||||
type UUIDRes struct {
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MD5Req computes an MD5 digest.
|
|
||||||
type MD5Req struct {
|
|
||||||
g.Meta `path:"/tools/md5" method:"post" tags:"Common/Tools" summary:"Compute MD5 digest"`
|
|
||||||
Text string `json:"text" v:"required#text required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MD5Res is the response of MD5Req.
|
|
||||||
type MD5Res struct {
|
|
||||||
MD5 string `json:"md5"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomReq generates a random string.
|
|
||||||
type RandomReq struct {
|
|
||||||
g.Meta `path:"/tools/random" method:"get" tags:"Common/Tools" summary:"Generate a random string"`
|
|
||||||
Length int `json:"length" d:"16" v:"min:1|max:128#length must be 1-128"`
|
|
||||||
Type string `json:"type" d:"alnum" v:"in:alnum,digits,letters#unsupported type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomRes is the response of RandomReq.
|
|
||||||
type RandomRes struct {
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeReq returns the current server time.
|
|
||||||
type TimeReq struct {
|
|
||||||
g.Meta `path:"/tools/time" method:"get" tags:"Common/Tools" summary:"Current server time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeRes is the response of TimeReq.
|
|
||||||
type TimeRes struct {
|
|
||||||
Timestamp int64 `json:"timestamp"` // unix seconds
|
|
||||||
DateTime string `json:"dateTime"` // 2006-01-02 15:04:05
|
|
||||||
Date string `json:"date"` // 2006-01-02
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPReq returns the caller's IP information.
|
|
||||||
type IPReq struct {
|
|
||||||
g.Meta `path:"/tools/ip" method:"get" tags:"Common/Tools" summary:"Client IP info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPRes is the response of IPReq.
|
|
||||||
type IPRes struct {
|
|
||||||
IP string `json:"ip"`
|
|
||||||
Internal bool `json:"internal"` // whether the IP is a private/internal address
|
|
||||||
}
|
|
||||||
14
api/open/v1/tools/doc.go
Normal file
14
api/open/v1/tools/doc.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Package tools hosts the open API tool endpoints (GET/POST /tools/*).
|
||||||
|
//
|
||||||
|
// Naming rule: every sub-feature gets its own directory under tools/, and the
|
||||||
|
// Req/Res contracts live in an index.go inside that directory, e.g.
|
||||||
|
//
|
||||||
|
// api/open/v1/tools/uuid/index.go - GET /tools/uuid
|
||||||
|
// api/open/v1/tools/md5/index.go - POST /tools/md5
|
||||||
|
// api/open/v1/tools/random/index.go - GET /tools/random
|
||||||
|
// api/open/v1/tools/time/index.go - GET /tools/time
|
||||||
|
// api/open/v1/tools/ip/index.go - GET /tools/ip
|
||||||
|
//
|
||||||
|
// Add a new feature by creating tools/<name>/index.go and a matching method
|
||||||
|
// in internal/controller/open/<name>.go; the router binds it automatically.
|
||||||
|
package tools
|
||||||
15
api/open/v1/tools/ip/index.go
Normal file
15
api/open/v1/tools/ip/index.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Package ip defines the GET /tools/ip endpoint contract.
|
||||||
|
package ip
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
// IPReq returns the caller's IP information.
|
||||||
|
type IPReq struct {
|
||||||
|
g.Meta `path:"/tools/ip" method:"get" tags:"Open/Tools" summary:"Client IP info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPRes is the response of IPReq.
|
||||||
|
type IPRes struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Internal bool `json:"internal"` // whether the IP is a private/internal address
|
||||||
|
}
|
||||||
15
api/open/v1/tools/md5/index.go
Normal file
15
api/open/v1/tools/md5/index.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Package md5 defines the POST /tools/md5 endpoint contract.
|
||||||
|
package md5
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
// MD5Req computes an MD5 digest.
|
||||||
|
type MD5Req struct {
|
||||||
|
g.Meta `path:"/tools/md5" method:"post" tags:"Open/Tools" summary:"Compute MD5 digest"`
|
||||||
|
Text string `json:"text" v:"required#text required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MD5Res is the response of MD5Req.
|
||||||
|
type MD5Res struct {
|
||||||
|
MD5 string `json:"md5"`
|
||||||
|
}
|
||||||
16
api/open/v1/tools/random/index.go
Normal file
16
api/open/v1/tools/random/index.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Package random defines the GET /tools/random endpoint contract.
|
||||||
|
package random
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
// RandomReq generates a random string.
|
||||||
|
type RandomReq struct {
|
||||||
|
g.Meta `path:"/tools/random" method:"get" tags:"Open/Tools" summary:"Generate a random string"`
|
||||||
|
Length int `json:"length" d:"16" v:"min:1|max:128#length must be 1-128"`
|
||||||
|
Type string `json:"type" d:"alnum" v:"in:alnum,digits,letters#unsupported type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomRes is the response of RandomReq.
|
||||||
|
type RandomRes struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
16
api/open/v1/tools/time/index.go
Normal file
16
api/open/v1/tools/time/index.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Package time defines the GET /tools/time endpoint contract.
|
||||||
|
package time
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
// TimeReq returns the current server time.
|
||||||
|
type TimeReq struct {
|
||||||
|
g.Meta `path:"/tools/time" method:"get" tags:"Open/Tools" summary:"Current server time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeRes is the response of TimeReq.
|
||||||
|
type TimeRes struct {
|
||||||
|
Timestamp int64 `json:"timestamp"` // unix seconds
|
||||||
|
DateTime string `json:"dateTime"` // 2006-01-02 15:04:05
|
||||||
|
Date string `json:"date"` // 2006-01-02
|
||||||
|
}
|
||||||
15
api/open/v1/tools/uuid/index.go
Normal file
15
api/open/v1/tools/uuid/index.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Package uuid defines the GET /tools/uuid endpoint contract.
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
// UUIDReq generates a unique ID.
|
||||||
|
type UUIDReq struct {
|
||||||
|
g.Meta `path:"/tools/uuid" method:"get" tags:"Open/Tools" summary:"Generate a unique ID"`
|
||||||
|
Short bool `json:"short"` // true: 8-char short code; false: 32-char ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUIDRes is the response of UUIDReq.
|
||||||
|
type UUIDRes struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
}
|
||||||
@ -83,3 +83,48 @@
|
|||||||
|
|
||||||
- 本次追加变更同样未提交 git,与上文合并为一次 commit。
|
- 本次追加变更同样未提交 git,与上文合并为一次 commit。
|
||||||
- 公共接口已开放无鉴权能力(md5/random/uuid 等),后续新增接口时需评审是否应限流/加签名,避免被滥用。
|
- 公共接口已开放无鉴权能力(md5/random/uuid 等),后续新增接口时需评审是否应限流/加签名,避免被滥用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 追加(18:05):开放接口命名决策 + API 目录重构
|
||||||
|
|
||||||
|
### 请求
|
||||||
|
|
||||||
|
1. 变更 API 目录设计:契约层改为 `tools/<子功能>/index.go` 结构(示例 `/api/common/v1/tools/ocr/index.go`)。
|
||||||
|
2. 同步修改所有涉及处(控制器、路由)。
|
||||||
|
3. 把命名规则记入项目记忆。
|
||||||
|
4. 咨询:公共接口一般用 common 命名吗?有没有更好的?
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
新增文件(契约层按子功能拆目录):
|
||||||
|
|
||||||
|
- `api/open/v1/tools/doc.go` — tools 目录结构规则说明
|
||||||
|
- `api/open/v1/tools/uuid/index.go`、`md5/index.go`、`random/index.go`、`time/index.go`、`ip/index.go` — 每子功能一个目录,index.go 内 `package <子功能名>`,g.Meta tags 改 `Open/Tools`
|
||||||
|
- `internal/controller/open/controller.go` — Controller 结构 + New()
|
||||||
|
- `internal/controller/open/uuid.go`、`md5.go`、`random.go`、`time.go`、`ip.go` — 按子功能拆文件,import 对应契约子包
|
||||||
|
|
||||||
|
删除文件:
|
||||||
|
|
||||||
|
- `api/common/v1/tools.go`、`api/common/` 目录
|
||||||
|
- `internal/controller/common/tools.go`、`internal/controller/common/` 目录
|
||||||
|
|
||||||
|
修改文件:
|
||||||
|
|
||||||
|
- `internal/cmd/cmd.go` — import `commonctl`→`openctl`;路由 `/api/common/v1`→`/api/open/v1`,注释改 Open tools API
|
||||||
|
- `AGENTS.md` — 目录树更新;「公共接口」小节改为「开放接口(api/open/v1)与命名规则」,记录 open 命名决策与 tools 子功能目录规则
|
||||||
|
- `PROJECT_STRUCTURE.md` — 目录树同步
|
||||||
|
|
||||||
|
验证:`go build ./...`、`go vet` 通过;冒烟测试:旧路由 `/api/common/v1/tools/time` 返回 404,新路由 `/api/open/v1/tools/*` 5 端点全部正确。
|
||||||
|
|
||||||
|
### 决策与理由
|
||||||
|
|
||||||
|
- **命名 common → open**:用户询问"公共接口一般用 common 吗",对比后选 **open**(业界开放接口惯例,如支付宝 /open/api;语义强调对外暴露、无鉴权)。`public` 为并列备选(更强调"公开"),`common` 偏内部通用语义、弃用。命名规则已记入 AGENTS.md「开放接口」小节。
|
||||||
|
- **契约层目录规则**:`api/open/v1/tools/<子功能名>/index.go` 每子功能一目录(与 GoFrame 惯例「api 下按功能分包」一致,用户示例 ocr 即为后续子功能,如未来加 OCR 识别接口即建 `tools/ocr/index.go`);控制器 `internal/controller/open/<子功能名>.go` 对应拆文件(同 package open)。
|
||||||
|
- **路由绑定不受目录重构影响**:GoFrame 通过 controller 方法参数反射定位 g.Meta,契约包拆成多个子包(uuid/md5/random/time/ip)不影响 `group.Bind(openctl.New())` 自动注册,cmd.go 只需改前缀。
|
||||||
|
- 包名 `time`(api/open/v1/tools/time)与标准库 time 潜在同名,controller 中统一用 import 别名 `timeapi` 规避。
|
||||||
|
|
||||||
|
### 待办与风险
|
||||||
|
|
||||||
|
- 前端若已联调旧 `/api/common/v1` 路径需同步改 `/api/open/v1`(当前无线上前端,风险低)。
|
||||||
|
- 新增子功能记得更新 `api/open/v1/tools/doc.go` 的示例清单。
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/gogf/gf/v2/os/gcmd"
|
"github.com/gogf/gf/v2/os/gcmd"
|
||||||
|
|
||||||
adminctl "service.xpcool.com/internal/controller/admin"
|
adminctl "service.xpcool.com/internal/controller/admin"
|
||||||
commonctl "service.xpcool.com/internal/controller/common"
|
|
||||||
"service.xpcool.com/internal/controller/hello"
|
"service.xpcool.com/internal/controller/hello"
|
||||||
|
openctl "service.xpcool.com/internal/controller/open"
|
||||||
userctl "service.xpcool.com/internal/controller/user"
|
userctl "service.xpcool.com/internal/controller/user"
|
||||||
"service.xpcool.com/internal/library/jwt"
|
"service.xpcool.com/internal/library/jwt"
|
||||||
"service.xpcool.com/internal/middleware"
|
"service.xpcool.com/internal/middleware"
|
||||||
@ -34,9 +34,9 @@ var (
|
|||||||
hello.NewV1(),
|
hello.NewV1(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
s.Group("/api/common/v1", func(group *ghttp.RouterGroup) {
|
s.Group("/api/open/v1", func(group *ghttp.RouterGroup) {
|
||||||
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
|
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
|
||||||
group.Bind(commonctl.New()) // Public common/tools API, no auth required.
|
group.Bind(openctl.New()) // Open tools API for frontends, no auth required.
|
||||||
})
|
})
|
||||||
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
|
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
|
||||||
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
|
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
// Package common implements the public common API (/api/common/v1).
|
|
||||||
// These controllers are thin adapters over the common/tools Go packages and
|
|
||||||
// require no authentication.
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
|
|
||||||
commonv1 "service.xpcool.com/api/common/v1"
|
|
||||||
"service.xpcool.com/common/tools/ip"
|
|
||||||
"service.xpcool.com/common/tools/md5"
|
|
||||||
"service.xpcool.com/common/tools/random"
|
|
||||||
"service.xpcool.com/common/tools/timex"
|
|
||||||
"service.xpcool.com/common/tools/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Controller implements the /api/common/v1 endpoints.
|
|
||||||
type Controller struct{}
|
|
||||||
|
|
||||||
// New creates a common API controller.
|
|
||||||
func New() *Controller { return &Controller{} }
|
|
||||||
|
|
||||||
// UUID generates a unique ID (32-char by default, 8-char when short=true).
|
|
||||||
func (c *Controller) UUID(ctx context.Context, req *commonv1.UUIDReq) (res *commonv1.UUIDRes, err error) {
|
|
||||||
if req.Short {
|
|
||||||
return &commonv1.UUIDRes{UUID: uuid.Short(8)}, nil
|
|
||||||
}
|
|
||||||
return &commonv1.UUIDRes{UUID: uuid.New()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MD5 computes the MD5 digest of the given text.
|
|
||||||
func (c *Controller) MD5(ctx context.Context, req *commonv1.MD5Req) (res *commonv1.MD5Res, err error) {
|
|
||||||
return &commonv1.MD5Res{MD5: md5.Md5Hex(req.Text)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random generates a random string of the requested type and length.
|
|
||||||
func (c *Controller) Random(ctx context.Context, req *commonv1.RandomReq) (res *commonv1.RandomRes, err error) {
|
|
||||||
var value string
|
|
||||||
switch req.Type {
|
|
||||||
case "digits":
|
|
||||||
value = random.Digits(req.Length)
|
|
||||||
case "letters":
|
|
||||||
value = random.Letters(req.Length)
|
|
||||||
default:
|
|
||||||
value = random.String(req.Length)
|
|
||||||
}
|
|
||||||
return &commonv1.RandomRes{Value: value}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns the current server timestamp and formatted time.
|
|
||||||
func (c *Controller) Time(ctx context.Context, req *commonv1.TimeReq) (res *commonv1.TimeRes, err error) {
|
|
||||||
now := timex.Now()
|
|
||||||
return &commonv1.TimeRes{
|
|
||||||
Timestamp: now.Timestamp(),
|
|
||||||
DateTime: now.Layout(timex.LayoutDateTime),
|
|
||||||
Date: now.Layout(timex.LayoutDate),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP returns the caller's IP and whether it is an internal address.
|
|
||||||
func (c *Controller) IP(ctx context.Context, req *commonv1.IPReq) (res *commonv1.IPRes, err error) {
|
|
||||||
clientIP := g.RequestFromCtx(ctx).GetClientIp()
|
|
||||||
return &commonv1.IPRes{IP: clientIP, Internal: ip.IsInternal(clientIP)}, nil
|
|
||||||
}
|
|
||||||
11
internal/controller/open/controller.go
Normal file
11
internal/controller/open/controller.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Package open implements the public open API (/api/open/v1).
|
||||||
|
// These controllers are thin adapters over the common/tools Go packages and
|
||||||
|
// require no authentication. Each sub-feature lives in its own file here,
|
||||||
|
// mirroring api/open/v1/tools/<name>/index.go.
|
||||||
|
package open
|
||||||
|
|
||||||
|
// Controller implements the /api/open/v1 endpoints.
|
||||||
|
type Controller struct{}
|
||||||
|
|
||||||
|
// New creates an open API controller.
|
||||||
|
func New() *Controller { return &Controller{} }
|
||||||
16
internal/controller/open/ip.go
Normal file
16
internal/controller/open/ip.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
ipapi "service.xpcool.com/api/open/v1/tools/ip"
|
||||||
|
"service.xpcool.com/common/tools/ip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IP returns the caller's IP and whether it is an internal address.
|
||||||
|
func (c *Controller) IP(ctx context.Context, req *ipapi.IPReq) (res *ipapi.IPRes, err error) {
|
||||||
|
clientIP := g.RequestFromCtx(ctx).GetClientIp()
|
||||||
|
return &ipapi.IPRes{IP: clientIP, Internal: ip.IsInternal(clientIP)}, nil
|
||||||
|
}
|
||||||
13
internal/controller/open/md5.go
Normal file
13
internal/controller/open/md5.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
md5api "service.xpcool.com/api/open/v1/tools/md5"
|
||||||
|
"service.xpcool.com/common/tools/md5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MD5 computes the MD5 digest of the given text.
|
||||||
|
func (c *Controller) MD5(ctx context.Context, req *md5api.MD5Req) (res *md5api.MD5Res, err error) {
|
||||||
|
return &md5api.MD5Res{MD5: md5.Md5Hex(req.Text)}, nil
|
||||||
|
}
|
||||||
22
internal/controller/open/random.go
Normal file
22
internal/controller/open/random.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
randomapi "service.xpcool.com/api/open/v1/tools/random"
|
||||||
|
"service.xpcool.com/common/tools/random"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Random generates a random string of the requested type and length.
|
||||||
|
func (c *Controller) Random(ctx context.Context, req *randomapi.RandomReq) (res *randomapi.RandomRes, err error) {
|
||||||
|
var value string
|
||||||
|
switch req.Type {
|
||||||
|
case "digits":
|
||||||
|
value = random.Digits(req.Length)
|
||||||
|
case "letters":
|
||||||
|
value = random.Letters(req.Length)
|
||||||
|
default:
|
||||||
|
value = random.String(req.Length)
|
||||||
|
}
|
||||||
|
return &randomapi.RandomRes{Value: value}, nil
|
||||||
|
}
|
||||||
18
internal/controller/open/time.go
Normal file
18
internal/controller/open/time.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
timeapi "service.xpcool.com/api/open/v1/tools/time"
|
||||||
|
"service.xpcool.com/common/tools/timex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time returns the current server timestamp and formatted time.
|
||||||
|
func (c *Controller) Time(ctx context.Context, req *timeapi.TimeReq) (res *timeapi.TimeRes, err error) {
|
||||||
|
now := timex.Now()
|
||||||
|
return &timeapi.TimeRes{
|
||||||
|
Timestamp: now.Timestamp(),
|
||||||
|
DateTime: now.Layout(timex.LayoutDateTime),
|
||||||
|
Date: now.Layout(timex.LayoutDate),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
16
internal/controller/open/uuid.go
Normal file
16
internal/controller/open/uuid.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
uuidapi "service.xpcool.com/api/open/v1/tools/uuid"
|
||||||
|
"service.xpcool.com/common/tools/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UUID generates a unique ID (32-char by default, 8-char when short=true).
|
||||||
|
func (c *Controller) UUID(ctx context.Context, req *uuidapi.UUIDReq) (res *uuidapi.UUIDRes, err error) {
|
||||||
|
if req.Short {
|
||||||
|
return &uuidapi.UUIDRes{UUID: uuid.Short(8)}, nil
|
||||||
|
}
|
||||||
|
return &uuidapi.UUIDRes{UUID: uuid.New()}, nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user