From 82b1ad2b5c048485ac63c34ac860402f91004e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E7=8A=80=E9=BA=9F?= Date: Mon, 24 Aug 2026 18:03:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor(api):=20=E5=85=AC=E5=85=B1=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=94=B9=E5=90=8D=20open=20=E5=B9=B6=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=A5=91=E7=BA=A6=E5=B1=82=E4=B8=BA=20tools//index.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 端点全部通过 --- AGENTS.md | 17 ++++--- PROJECT_STRUCTURE.md | 5 +- api/common/v1/tools.go | 65 ------------------------- api/open/v1/tools/doc.go | 14 ++++++ api/open/v1/tools/ip/index.go | 15 ++++++ api/open/v1/tools/md5/index.go | 15 ++++++ api/open/v1/tools/random/index.go | 16 +++++++ api/open/v1/tools/time/index.go | 16 +++++++ api/open/v1/tools/uuid/index.go | 15 ++++++ docs/change-log/2026-08-24.md | 45 ++++++++++++++++++ internal/cmd/cmd.go | 6 +-- internal/controller/common/tools.go | 66 -------------------------- internal/controller/open/controller.go | 11 +++++ internal/controller/open/ip.go | 16 +++++++ internal/controller/open/md5.go | 13 +++++ internal/controller/open/random.go | 22 +++++++++ internal/controller/open/time.go | 18 +++++++ internal/controller/open/uuid.go | 16 +++++++ 18 files changed, 248 insertions(+), 143 deletions(-) delete mode 100644 api/common/v1/tools.go create mode 100644 api/open/v1/tools/doc.go create mode 100644 api/open/v1/tools/ip/index.go create mode 100644 api/open/v1/tools/md5/index.go create mode 100644 api/open/v1/tools/random/index.go create mode 100644 api/open/v1/tools/time/index.go create mode 100644 api/open/v1/tools/uuid/index.go delete mode 100644 internal/controller/common/tools.go create mode 100644 internal/controller/open/controller.go create mode 100644 internal/controller/open/ip.go create mode 100644 internal/controller/open/md5.go create mode 100644 internal/controller/open/random.go create mode 100644 internal/controller/open/time.go create mode 100644 internal/controller/open/uuid.go diff --git a/AGENTS.md b/AGENTS.md index 6345eae..3ed19ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,15 +23,16 @@ ``` service.xpcool.com/ ├── api/ # HTTP 契约与 Swagger 元数据 -│ ├── user/v1/ # /api/v1 用户端 API(登录公开,其余走 UserAuth) -│ ├── admin/v1/ # /admin/v1 管理端 API(AdminAuth + X-Permission) -│ └── common/v1/ # /api/common/v1 公共接口(给前端调用,公开无鉴权) +│ ├── user/v1/ # /api/v1 用户端 API(登录公开,其余走 UserAuth) +│ ├── admin/v1/ # /admin/v1 管理端 API(AdminAuth + X-Permission) +│ └── open/v1/ # /api/open/v1 开放接口(给前端调用,公开无鉴权) +│ └── tools/ # 工具子功能契约,每子功能一目录:tools//index.go ├── common/ # 公共可复用模块(不依赖 internal,可独立抽取成库) │ └── tools/ # 工具模块,按子功能分包(见下文) ├── internal/ │ ├── cmd/ # 启动引导(路由分组注册在此) │ ├── consts/ # 错误码与常量 -│ ├── controller/ # API→service 适配层(不写业务;含 common/ 公共接口实现) +│ ├── controller/ # API→service 适配层(不写业务;含 open/ 开放接口实现) │ ├── service/ # 领域用例(业务逻辑直接写这里,不用 logic/) │ ├── dao/ # gf gen dao 生成,禁止手改 │ ├── model/ # entity/ do/ dto/ vo/(entity、do 生成,禁止手改) @@ -43,11 +44,13 @@ service.xpcool.com/ └── 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 包。 -- 新增公共接口:在 `api/common/v1` 加 Req/Res(g.Meta 带 path/method),在 `internal/controller/common` 加方法,`internal/cmd/cmd.go` 的 `/api/common/v1` 分组会自动绑定。 +- **新增子功能三步**:① `api/open/v1/tools//index.go` 写 Req/Res(g.Meta 带 path/method);② `internal/controller/open/.go` 加方法;③ 路由自动绑定,无需改 cmd.go。 ## 公共工具模块 common/tools diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index e691236..0e46967 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -5,14 +5,15 @@ service.xpcool.com/ ├── api/ # HTTP contracts and Swagger metadata │ ├── user/v1/ # /api/v1 - client-facing 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//index.go ├── common/ # public reusable module (no internal/ deps) │ └── tools/ # utility toolbox: md5, cryptox, uuid, random, │ # timex, convertx, strx, slicex, ip, filex ├── internal/ │ ├── cmd/ # application bootstrap and route isolation │ ├── 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 │ ├── dao/ # generated by gf gen dao; never hand edited │ ├── model/ diff --git a/api/common/v1/tools.go b/api/common/v1/tools.go deleted file mode 100644 index 9bd84b9..0000000 --- a/api/common/v1/tools.go +++ /dev/null @@ -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 -} diff --git a/api/open/v1/tools/doc.go b/api/open/v1/tools/doc.go new file mode 100644 index 0000000..6154071 --- /dev/null +++ b/api/open/v1/tools/doc.go @@ -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//index.go and a matching method +// in internal/controller/open/.go; the router binds it automatically. +package tools diff --git a/api/open/v1/tools/ip/index.go b/api/open/v1/tools/ip/index.go new file mode 100644 index 0000000..211977c --- /dev/null +++ b/api/open/v1/tools/ip/index.go @@ -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 +} diff --git a/api/open/v1/tools/md5/index.go b/api/open/v1/tools/md5/index.go new file mode 100644 index 0000000..cc3e13f --- /dev/null +++ b/api/open/v1/tools/md5/index.go @@ -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"` +} diff --git a/api/open/v1/tools/random/index.go b/api/open/v1/tools/random/index.go new file mode 100644 index 0000000..3bbc346 --- /dev/null +++ b/api/open/v1/tools/random/index.go @@ -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"` +} diff --git a/api/open/v1/tools/time/index.go b/api/open/v1/tools/time/index.go new file mode 100644 index 0000000..c88e738 --- /dev/null +++ b/api/open/v1/tools/time/index.go @@ -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 +} diff --git a/api/open/v1/tools/uuid/index.go b/api/open/v1/tools/uuid/index.go new file mode 100644 index 0000000..47244cc --- /dev/null +++ b/api/open/v1/tools/uuid/index.go @@ -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"` +} diff --git a/docs/change-log/2026-08-24.md b/docs/change-log/2026-08-24.md index 4cbdbf9..47dbd2b 100644 --- a/docs/change-log/2026-08-24.md +++ b/docs/change-log/2026-08-24.md @@ -83,3 +83,48 @@ - 本次追加变更同样未提交 git,与上文合并为一次 commit。 - 公共接口已开放无鉴权能力(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` 的示例清单。 diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 6fd3c15..5a39561 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -8,8 +8,8 @@ import ( "github.com/gogf/gf/v2/os/gcmd" adminctl "service.xpcool.com/internal/controller/admin" - commonctl "service.xpcool.com/internal/controller/common" "service.xpcool.com/internal/controller/hello" + openctl "service.xpcool.com/internal/controller/open" userctl "service.xpcool.com/internal/controller/user" "service.xpcool.com/internal/library/jwt" "service.xpcool.com/internal/middleware" @@ -34,9 +34,9 @@ var ( 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.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) { group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse) diff --git a/internal/controller/common/tools.go b/internal/controller/common/tools.go deleted file mode 100644 index eb37454..0000000 --- a/internal/controller/common/tools.go +++ /dev/null @@ -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 -} diff --git a/internal/controller/open/controller.go b/internal/controller/open/controller.go new file mode 100644 index 0000000..2c69a0d --- /dev/null +++ b/internal/controller/open/controller.go @@ -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//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{} } diff --git a/internal/controller/open/ip.go b/internal/controller/open/ip.go new file mode 100644 index 0000000..c335ed2 --- /dev/null +++ b/internal/controller/open/ip.go @@ -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 +} diff --git a/internal/controller/open/md5.go b/internal/controller/open/md5.go new file mode 100644 index 0000000..9bb7169 --- /dev/null +++ b/internal/controller/open/md5.go @@ -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 +} diff --git a/internal/controller/open/random.go b/internal/controller/open/random.go new file mode 100644 index 0000000..bec7b8b --- /dev/null +++ b/internal/controller/open/random.go @@ -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 +} diff --git a/internal/controller/open/time.go b/internal/controller/open/time.go new file mode 100644 index 0000000..a2b24c0 --- /dev/null +++ b/internal/controller/open/time.go @@ -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 +} diff --git a/internal/controller/open/uuid.go b/internal/controller/open/uuid.go new file mode 100644 index 0000000..67d955e --- /dev/null +++ b/internal/controller/open/uuid.go @@ -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 +}