Slim down service module: remove redundant features and optimize ID generation (by AI)

This commit is contained in:
AI Engineer 2026-05-08 22:41:01 +08:00
parent bdb104aa2f
commit 5b63fd83a9
10 changed files with 129 additions and 364 deletions

View File

@ -3,10 +3,11 @@
极简、自动化的 Web 与 WebSocket 服务框架,实现极致的依赖注入与路由映射。 极简、自动化的 Web 与 WebSocket 服务框架,实现极致的依赖注入与路由映射。
## 核心特性 ## 核心特性
- **极致精简**: 剥离非核心组件(如 Starter, Task, 业务 Result 定义),保持底座纯净。
- **路由反射**: 自动解析函数参数,支持 `*Request`, `*Response`, `*log.Logger` 及自定义结构体自动注入。 - **路由反射**: 自动解析函数参数,支持 `*Request`, `*Response`, `*log.Logger` 及自定义结构体自动注入。
- **自动校验**: 集成 `verify` 引擎,通过 Struct Tag 实现入参合法性自动检查。 - **自动校验**: 集成 `verify` 引擎,通过 Struct Tag 实现入参合法性自动检查。
- **功能闭环**: 内置静态文件服务、WebSocket (带 Action 路由)、URL 重写、反向代理(对接 Discover - **功能闭环**: 内置静态文件服务、基础 WebSocket 注册、URL 重写、反向代理(对接 Discover
- **零摩擦启动**: 支持命令行指令管理 (start/stop/help) 及异步平滑启停 - **统一 ID 体系**: 整合 Redis 集群版 ID 生成器,全局统一生成 `RequestId``LogTraceID`
## API 指南 ## API 指南
@ -14,39 +15,42 @@
```go ```go
import "apigo.cc/go/service" import "apigo.cc/go/service"
// 注册标准 Web 服务 // 注册标准 Web 服务,自动注入 Struct 参数并执行校验
service.Register(0, "/hello", func(in struct{ Name string }) string { service.Register(0, "/hello", func(in struct{ Name string `verify:"length:2+"` }) string {
return "Hello " + in.Name return "Hello " + in.Name
}, "打招呼接口") }, "打招呼接口")
// 注册 Restful 服务
service.Restful(0, "POST", "/user/{id}", func(args map[string]any) service.Result {
res := service.Result{}
res.OK()
return res
}, "更新用户")
``` ```
### 2. WebSocket 支持 ### 2. WebSocket 支持 (极简模式)
```go ```go
ar := service.RegisterWebsocket(0, "/ws", onOpen, onClose, "聊天室") // 业务自行处理消息循环与逻辑
ar.RegisterAction(0, "chat", func(in ChatMessage, sess *MySession) { service.RegisterWebsocket(0, "/ws", func(conn *websocket.Conn, logger *log.Logger) {
// 处理消息 defer conn.Close()
}, "发送消息") for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
logger.Info("received", "msg", string(msg))
}
}, "聊天室")
``` ```
### 3. 增强插件 ### 3. 生命周期管理
```go
func main() {
// 异步启动
as := service.AsyncStart()
// 执行其他初始化...
as.Wait() // 阻塞并监听信号优雅退出
}
```
### 4. 增强插件
- **静态文件**: `service.Static("/ui", "./static_dir")` - **静态文件**: `service.Static("/ui", "./static_dir")`
- **URL 重写**: `service.Rewrite("/old", "/new")` - **URL 重写**: `service.Rewrite("/old", "/new")`
- **反向代理**: `service.Proxy(0, "/api", "other_app", "/api")` - **反向代理**: `service.Proxy(0, "/api", "other_app", "/api")`
- **文档生成**: `service.MakeDocument()` 返回全量接口描述
### 4. 生命周期管理
```go
func main() {
service.CheckCmd() // 处理 start/stop/help 指令
service.Start() // 阻塞启动
}
```
## 基础设施对齐 ## 基础设施对齐
- **类型转换**: `apigo.cc/go/cast` - **类型转换**: `apigo.cc/go/cast`

View File

@ -77,10 +77,10 @@ func MakeDocument() []Api {
AuthLevel: a.authLevel, AuthLevel: a.authLevel,
Memo: a.memo, Memo: a.memo,
} }
if a.openFuncType != nil && a.openFuncType.NumIn() > 0 { if a.handlerType != nil && a.handlerType.NumIn() > 0 {
// Find struct in // Find struct in
for i := 0; i < a.openFuncType.NumIn(); i++ { for i := 0; i < a.handlerType.NumIn(); i++ {
t := a.openFuncType.In(i) t := a.handlerType.In(i)
if t.Kind() == reflect.Struct { if t.Kind() == reflect.Struct {
api.In = getType(t) api.In = getType(t)
break break
@ -88,22 +88,6 @@ func MakeDocument() []Api {
} }
} }
out = append(out, api) out = append(out, api)
for name, act := range a.actions {
actionApi := Api{
Type: "Action",
Path: name,
AuthLevel: act.authLevel,
Memo: act.memo,
}
if act.inType != nil {
actionApi.In = getType(act.inType)
}
if act.funcType.NumOut() > 0 {
actionApi.Out = getType(act.funcType.Out(0))
}
out = append(out, actionApi)
}
} }
websocketServicesLock.RUnlock() websocketServicesLock.RUnlock()
@ -153,10 +137,3 @@ func getType(t reflect.Type) any {
return t.String() return t.String()
} }
} }
// 自动注册文档服务
func init() {
Register(0, "/__DOC__", func() string {
return MakeJsonDocument()
}, "API Document")
}

View File

@ -2,7 +2,6 @@ package service
import ( import (
"apigo.cc/go/cast" "apigo.cc/go/cast"
"apigo.cc/go/id"
"apigo.cc/go/log" "apigo.cc/go/log"
"apigo.cc/go/standard" "apigo.cc/go/standard"
"encoding/json" "encoding/json"
@ -25,7 +24,7 @@ func (rh *routeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
startTime := time.Now() startTime := time.Now()
requestId := r.Header.Get(standard.DiscoverHeaderRequestId) requestId := r.Header.Get(standard.DiscoverHeaderRequestId)
if requestId == "" { if requestId == "" {
requestId = id.MakeID(12) requestId = MakeId(12)
r.Header.Set(standard.DiscoverHeaderRequestId, requestId) r.Header.Set(standard.DiscoverHeaderRequestId, requestId)
} }
@ -278,7 +277,7 @@ func handleClientKeys(request *Request, response *Response) {
if sessionIdMaker != nil { if sessionIdMaker != nil {
sessionId = sessionIdMaker() sessionId = sessionIdMaker()
} else { } else {
sessionId = id.MakeID(14) sessionId = MakeId(14)
} }
if !Config.SessionWithoutCookie { if !Config.SessionWithoutCookie {
http.SetCookie(response.Writer, &http.Cookie{ http.SetCookie(response.Writer, &http.Cookie{
@ -302,7 +301,7 @@ func handleClientKeys(request *Request, response *Response) {
} }
} }
if deviceId == "" { if deviceId == "" {
deviceId = id.MakeID(14) deviceId = MakeId(14)
if !Config.DeviceWithoutCookie { if !Config.DeviceWithoutCookie {
http.SetCookie(response.Writer, &http.Cookie{ http.SetCookie(response.Writer, &http.Cookie{
Name: usedDeviceIdKey, Name: usedDeviceIdKey,

View File

@ -9,6 +9,12 @@ import (
"sync" "sync"
) )
// Map 通用 Map 类型
type Map = map[string]any
// Arr 通用切片类型
type Arr = []any
// WebServiceOptions 服务注册选项 // WebServiceOptions 服务注册选项
type WebServiceOptions struct { type WebServiceOptions struct {
Priority int Priority int
@ -58,9 +64,8 @@ var (
webServicesList = make([]*webServiceType, 0) webServicesList = make([]*webServiceType, 0)
websocketServices = make(map[string]*websocketServiceType) websocketServices = make(map[string]*websocketServiceType)
regexWebsocketServices = make([]*websocketServiceType, 0)
websocketServicesLock = sync.RWMutex{} websocketServicesLock = sync.RWMutex{}
websocketServicesList = make([]*websocketServiceType, 0) websocketServicesList = make([]*webServiceType, 0)
// 过滤器与拦截器 // 过滤器与拦截器
inFilters = make([]func(*map[string]any, *Request, *Response, *log.Logger) any, 0) inFilters = make([]func(*map[string]any, *Request, *Response, *log.Logger) any, 0)

View File

@ -1,49 +0,0 @@
package service
import (
"fmt"
"os"
"path/filepath"
)
// StartCmd 命令行命令定义
type StartCmd struct {
Name string
Comment string
Func func()
}
var startCmds = []StartCmd{
{"start", "Start server", Start},
}
// AddCmd 添加自定义命令行命令
func AddCmd(name, comment string, function func()) {
startCmds = append(startCmds, StartCmd{name, comment, function})
}
// CheckCmd 检查并执行命令行命令
func CheckCmd() {
if len(os.Args) > 1 {
cmd := os.Args[1]
if cmd == "help" || cmd == "--help" {
showHelp()
os.Exit(0)
}
for _, cmdInfo := range startCmds {
if cmd == cmdInfo.Name {
cmdInfo.Func()
os.Exit(0)
}
}
}
}
func showHelp() {
fmt.Printf("Usage: %s [command]\n\n", filepath.Base(os.Args[0]))
fmt.Println("Available commands:")
for _, cmdInfo := range startCmds {
fmt.Printf(" %-10s %s\n", cmdInfo.Name, cmdInfo.Comment)
}
}

View File

@ -1,68 +0,0 @@
package service
// Map 通用 Map 类型
type Map = map[string]any
// Arr 通用切片类型
type Arr = []any
// Argot 错误码/标识符类型
type Argot string
// Result 通用返回结构
type Result struct {
Ok bool `json:"ok"`
Argot Argot `json:"argot,omitempty"`
Message string `json:"message,omitempty"`
}
// CodeResult 带状态码的返回结构
type CodeResult struct {
Code int `json:"code"`
Message string `json:"message,omitempty"`
}
// ArgotInfo 标识符信息(用于文档生成)
type ArgotInfo struct {
Name Argot
Memo string
}
// OK 设置成功状态
func (r *Result) OK(argots ...Argot) {
r.Ok = true
if len(argots) > 0 {
r.Argot = argots[0]
}
}
// Failed 设置失败状态
func (r *Result) Failed(message string, argots ...Argot) {
r.Ok = false
r.Message = message
if len(argots) > 0 {
r.Argot = argots[0]
}
}
// Done 根据布尔值设置状态
func (r *Result) Done(ok bool, failedMessage string, argots ...Argot) {
r.Ok = ok
if !ok {
r.Message = failedMessage
if len(argots) > 0 {
r.Argot = argots[0]
}
}
}
// OK 设置成功状态 (Code=1)
func (r *CodeResult) OK() {
r.Code = 1
}
// Failed 设置失败状态与错误码
func (r *CodeResult) Failed(code int, message string) {
r.Code = code
r.Message = message
}

View File

@ -1,41 +0,0 @@
package service
import (
"testing"
)
func TestResult(t *testing.T) {
r := &Result{}
r.OK()
if !r.Ok {
t.Error("Result.OK() failed")
}
r.Failed("error", Argot("ERR_CODE"))
if r.Ok || r.Message != "error" || r.Argot != "ERR_CODE" {
t.Error("Result.Failed() failed")
}
r.Done(true, "never")
if !r.Ok {
t.Error("Result.Done(true) failed")
}
r.Done(false, "failed", Argot("FAIL"))
if r.Ok || r.Message != "failed" || r.Argot != "FAIL" {
t.Error("Result.Done(false) failed")
}
}
func TestCodeResult(t *testing.T) {
cr := &CodeResult{}
cr.OK()
if cr.Code != 1 {
t.Error("CodeResult.OK() failed")
}
cr.Failed(500, "internal error")
if cr.Code != 500 || cr.Message != "internal error" {
t.Error("CodeResult.Failed() failed")
}
}

View File

@ -2,19 +2,60 @@ package service
import ( import (
"apigo.cc/go/id" "apigo.cc/go/id"
"apigo.cc/go/log"
"apigo.cc/go/redis"
"sync"
) )
var (
idMaker IDMakerInterface
idMakerLock sync.Mutex
)
// IDMakerInterface ID 生成器接口
type IDMakerInterface interface {
Get(size int) string
GetForMysql(size int) string
GetForPostgreSQL(size int) string
}
func getIDMaker() IDMakerInterface {
if idMaker != nil {
return idMaker
}
idMakerLock.Lock()
defer idMakerLock.Unlock()
if idMaker != nil {
return idMaker
}
if Config.IdServer != "" {
rd := redis.GetRedis(Config.IdServer, log.DefaultLogger)
if rd.Error == nil {
idMaker = redis.NewIDMaker(rd)
}
}
if idMaker == nil {
idMaker = id.DefaultIDMaker
}
return idMaker
}
// MakeId 生成指定长度的 ID // MakeId 生成指定长度的 ID
func MakeId(size int) string { func MakeId(size int) string {
return id.MakeID(size) return getIDMaker().Get(size)
} }
// MakeIdForMysql 生成适用于 MySQL 的有序 ID // MakeIdForMysql 生成适用于 MySQL 的有序 ID
func MakeIdForMysql(size int) string { func MakeIdForMysql(size int) string {
return id.DefaultIDMaker.GetForMysql(size) return getIDMaker().GetForMysql(size)
} }
// MakeIdForPostgreSQL 生成适用于 PostgreSQL 的有序 ID // MakeIdForPostgreSQL 生成适用于 PostgreSQL 的有序 ID
func MakeIdForPostgreSQL(size int) string { func MakeIdForPostgreSQL(size int) string {
return id.DefaultIDMaker.GetForPostgreSQL(size) return getIDMaker().GetForPostgreSQL(size)
} }

View File

@ -1,97 +1,42 @@
package service package service
import ( import (
"apigo.cc/go/cast"
"apigo.cc/go/log" "apigo.cc/go/log"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"net/http" "net/http"
"reflect" "reflect"
"regexp"
) )
// websocketServiceType WebSocket 服务元数据 // websocketServiceType WebSocket 服务元数据
type websocketServiceType struct { type websocketServiceType struct {
authLevel int authLevel int
path string path string
pathMatcher *regexp.Regexp
pathArgs []string
updater *websocket.Upgrader updater *websocket.Upgrader
openFuncValue reflect.Value handlerValue reflect.Value
openFuncType reflect.Type handlerType reflect.Type
closeFuncValue reflect.Value
closeFuncType reflect.Type
sessionType reflect.Type
actions map[string]*websocketActionType
isSimple bool
options WebServiceOptions
memo string memo string
} }
// websocketActionType WebSocket Action 元数据
type websocketActionType struct {
authLevel int
funcValue reflect.Value
funcType reflect.Type
inType reflect.Type
memo string
}
// ActionRegister WebSocket Action 注册器
type ActionRegister struct {
ws *websocketServiceType
}
// RegisterWebsocket 注册 WebSocket 服务 // RegisterWebsocket 注册 WebSocket 服务
func RegisterWebsocket(authLevel int, path string, onOpen, onClose any, memo string) *ActionRegister { func RegisterWebsocket(authLevel int, path string, handler any, memo string) {
v := reflect.ValueOf(handler)
t := v.Type()
if t.Kind() != reflect.Func {
return
}
s := &websocketServiceType{ s := &websocketServiceType{
authLevel: authLevel, authLevel: authLevel,
path: path, path: path,
memo: memo, memo: memo,
updater: &websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}, updater: &websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }},
actions: make(map[string]*websocketActionType), handlerValue: v,
} handlerType: t,
if onOpen != nil {
s.openFuncValue = reflect.ValueOf(onOpen)
s.openFuncType = s.openFuncValue.Type()
if s.openFuncType.NumOut() > 0 {
s.sessionType = s.openFuncType.Out(0)
}
}
if onClose != nil {
s.closeFuncValue = reflect.ValueOf(onClose)
s.closeFuncType = s.closeFuncValue.Type()
} }
websocketServicesLock.Lock() websocketServicesLock.Lock()
websocketServices[path] = s websocketServices[path] = s
websocketServicesLock.Unlock() websocketServicesLock.Unlock()
return &ActionRegister{ws: s}
}
// RegisterAction 注册 WebSocket Action
func (ar *ActionRegister) RegisterAction(authLevel int, name string, action any, memo string) {
v := reflect.ValueOf(action)
t := v.Type()
a := &websocketActionType{
authLevel: authLevel,
funcValue: v,
funcType: t,
memo: memo,
}
// 查找输入参数类型
for i := 0; i < t.NumIn(); i++ {
inT := t.In(i)
if inT.Kind() == reflect.Struct {
a.inType = inT
break
}
}
ar.ws.actions[name] = a
} }
func doWebsocketService(ws *websocketServiceType, request *Request, response *Response, logger *log.Logger) { func doWebsocketService(ws *websocketServiceType, request *Request, response *Response, logger *log.Logger) {
@ -102,74 +47,21 @@ func doWebsocketService(ws *websocketServiceType, request *Request, response *Re
} }
defer conn.Close() defer conn.Close()
var session any // 调用业务处理函数,注入依赖
if ws.openFuncValue.IsValid() { params := make([]reflect.Value, ws.handlerType.NumIn())
// 简化版:仅支持基础参数注入
params := make([]reflect.Value, ws.openFuncType.NumIn())
for i := 0; i < len(params); i++ { for i := 0; i < len(params); i++ {
t := ws.openFuncType.In(i) t := ws.handlerType.In(i)
if t == reflect.TypeOf(request) { if t == reflect.TypeOf(request) {
params[i] = reflect.ValueOf(request) params[i] = reflect.ValueOf(request)
} else if t == reflect.TypeOf(logger) { } else if t == reflect.TypeOf(logger) {
params[i] = reflect.ValueOf(logger) params[i] = reflect.ValueOf(logger)
} else {
params[i] = reflect.New(t).Elem()
}
}
outs := ws.openFuncValue.Call(params)
if len(outs) > 0 {
session = outs[0].Interface()
}
}
for {
var msg Map
if err := conn.ReadJSON(&msg); err != nil {
break
}
actionName := cast.String(msg["action"])
action := ws.actions[actionName]
if action == nil {
action = ws.actions[""] // 默认 action
}
if action != nil {
params := make([]reflect.Value, action.funcType.NumIn())
for i := 0; i < len(params); i++ {
t := action.funcType.In(i)
if t == ws.sessionType {
params[i] = reflect.ValueOf(session)
} else if t == reflect.TypeOf(conn) { } else if t == reflect.TypeOf(conn) {
params[i] = reflect.ValueOf(conn) params[i] = reflect.ValueOf(conn)
} else if t.Kind() == reflect.Struct { } else if obj := GetInject(t); obj != nil {
in := reflect.New(t).Interface() params[i] = reflect.ValueOf(obj)
cast.Convert(in, msg)
params[i] = reflect.ValueOf(in).Elem()
} else { } else {
params[i] = reflect.New(t).Elem() params[i] = reflect.New(t).Elem()
} }
} }
outs := action.funcValue.Call(params) ws.handlerValue.Call(params)
if len(outs) > 0 {
result := outs[0].Interface()
if result != nil {
_ = conn.WriteJSON(result)
}
}
}
}
if ws.closeFuncValue.IsValid() {
params := make([]reflect.Value, ws.closeFuncType.NumIn())
for i := 0; i < len(params); i++ {
t := ws.closeFuncType.In(i)
if t == ws.sessionType {
params[i] = reflect.ValueOf(session)
} else {
params[i] = reflect.New(t).Elem()
}
}
ws.closeFuncValue.Call(params)
}
} }

View File

@ -9,10 +9,15 @@ import (
func TestWebSocketService(t *testing.T) { func TestWebSocketService(t *testing.T) {
// 注册 WebSocket 服务 // 注册 WebSocket 服务
ar := RegisterWebsocket(0, "/ws", nil, nil, "test websocket") RegisterWebsocket(0, "/ws", func(conn *websocket.Conn) {
ar.RegisterAction(0, "echo", func(in struct{ Msg string }) Map { for {
return Map{"action": "echo", "reply": in.Msg} var msg Map
}, "echo action") if err := conn.ReadJSON(&msg); err != nil {
break
}
_ = conn.WriteJSON(Map{"reply": msg["msg"]})
}
}, "test websocket")
// 启动测试服务器 // 启动测试服务器
server := httptest.NewServer(&routeHandler{}) server := httptest.NewServer(&routeHandler{})