feat: 新增客户修改、删除、更新状态接口,成功的错误信息返回优化

This commit is contained in:
wood911010 committed 2025-08-16 17:10:43 +08:00
1 parent d20f79782f
commit f0762c9d78
18 files changed
+1242 -108

No files matched your search

-1
View File
@@ -5,7 +5,6 @@ system:
errors:
# Success status
"000000": "Success"
"000001": "Customer created successfully"
# Authentication module (10xxxx)
auth:
-1
View File
@@ -5,7 +5,6 @@ system:
errors:
# 成功状态
"000000": "成功"
"000001": "客户创建成功"
# 认证模块 (10xxxx)
auth:
+317 -2
View File
@@ -305,6 +305,227 @@ const docTemplate = `{
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "更新客户信息,所有字段都是可选的",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "更新客户信息",
"parameters": [
{
"type": "string",
"description": "客户ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "客户信息",
"name": "customer",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CustomerUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "更新成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Customer"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
},
"delete": {
"security": [
{
"BearerAuth": []
}
],
"description": "软删除客户记录",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "删除客户",
"parameters": [
{
"type": "string",
"description": "客户ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "删除成功",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/customers/{id}/status": {
"patch": {
"security": [
{
"BearerAuth": []
}
],
"description": "修改客户状态(激活/停用)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "修改客户状态",
"parameters": [
{
"type": "string",
"description": "客户ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "状态信息",
"name": "status",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CustomerStatusUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "状态更新成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Customer"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/enums": {
@@ -627,7 +848,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"code": {
"type": "integer"
"type": "string"
},
"data": {},
"message": {
@@ -855,6 +1076,100 @@ const docTemplate = `{
}
}
},
"models.CustomerStatusUpdateRequest": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"description": "状态,必填",
"type": "string",
"enum": [
"active",
"disabled"
]
}
}
},
"models.CustomerUpdateRequest": {
"type": "object",
"properties": {
"address": {
"description": "地址,可选",
"type": "string",
"maxLength": 500
},
"company_size": {
"description": "企业规模,可选",
"type": "string",
"enum": [
"small",
"medium",
"large",
"enterprise"
]
},
"contact_person": {
"description": "联系人姓名,可选",
"type": "string",
"maxLength": 100
},
"contact_title": {
"description": "联系人职位,可选",
"type": "string",
"maxLength": 100
},
"customer_level": {
"description": "客户等级,可选",
"type": "string",
"enum": [
"normal",
"vip",
"enterprise",
"strategic"
]
},
"customer_name": {
"description": "客户名称,可选",
"type": "string",
"maxLength": 200
},
"customer_type": {
"description": "客户类型,可选",
"type": "string",
"enum": [
"individual",
"enterprise",
"government",
"education"
]
},
"description": {
"description": "描述,可选",
"type": "string",
"maxLength": 1000
},
"email": {
"description": "邮箱,可选",
"type": "string",
"maxLength": 255
},
"phone": {
"description": "电话,可选",
"type": "string",
"maxLength": 20
},
"status": {
"description": "状态,可选",
"type": "string",
"enum": [
"active",
"disabled"
]
}
}
},
"models.EnumItem": {
"type": "object",
"properties": {
@@ -958,7 +1273,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"code": {
"type": "integer"
"type": "string"
},
"data": {},
"message": {
+317 -2
View File
@@ -302,6 +302,227 @@
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "更新客户信息,所有字段都是可选的",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "更新客户信息",
"parameters": [
{
"type": "string",
"description": "客户ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "客户信息",
"name": "customer",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CustomerUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "更新成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Customer"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
},
"delete": {
"security": [
{
"BearerAuth": []
}
],
"description": "软删除客户记录",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "删除客户",
"parameters": [
{
"type": "string",
"description": "客户ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "删除成功",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/customers/{id}/status": {
"patch": {
"security": [
{
"BearerAuth": []
}
],
"description": "修改客户状态(激活/停用)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "修改客户状态",
"parameters": [
{
"type": "string",
"description": "客户ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "状态信息",
"name": "status",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CustomerStatusUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "状态更新成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Customer"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/enums": {
@@ -624,7 +845,7 @@
"type": "object",
"properties": {
"code": {
"type": "integer"
"type": "string"
},
"data": {},
"message": {
@@ -852,6 +1073,100 @@
}
}
},
"models.CustomerStatusUpdateRequest": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"description": "状态,必填",
"type": "string",
"enum": [
"active",
"disabled"
]
}
}
},
"models.CustomerUpdateRequest": {
"type": "object",
"properties": {
"address": {
"description": "地址,可选",
"type": "string",
"maxLength": 500
},
"company_size": {
"description": "企业规模,可选",
"type": "string",
"enum": [
"small",
"medium",
"large",
"enterprise"
]
},
"contact_person": {
"description": "联系人姓名,可选",
"type": "string",
"maxLength": 100
},
"contact_title": {
"description": "联系人职位,可选",
"type": "string",
"maxLength": 100
},
"customer_level": {
"description": "客户等级,可选",
"type": "string",
"enum": [
"normal",
"vip",
"enterprise",
"strategic"
]
},
"customer_name": {
"description": "客户名称,可选",
"type": "string",
"maxLength": 200
},
"customer_type": {
"description": "客户类型,可选",
"type": "string",
"enum": [
"individual",
"enterprise",
"government",
"education"
]
},
"description": {
"description": "描述,可选",
"type": "string",
"maxLength": 1000
},
"email": {
"description": "邮箱,可选",
"type": "string",
"maxLength": 255
},
"phone": {
"description": "电话,可选",
"type": "string",
"maxLength": 20
},
"status": {
"description": "状态,可选",
"type": "string",
"enum": [
"active",
"disabled"
]
}
}
},
"models.EnumItem": {
"type": "object",
"properties": {
@@ -955,7 +1270,7 @@
"type": "object",
"properties": {
"code": {
"type": "integer"
"type": "string"
},
"data": {},
"message": {
+211 -2
View File
@@ -3,7 +3,7 @@ definitions:
models.APIResponse:
properties:
code:
type: integer
type: string
data: {}
message:
type: string
@@ -164,6 +164,78 @@ definitions:
total_pages:
type: integer
type: object
models.CustomerStatusUpdateRequest:
properties:
status:
description: 状态,必填
enum:
- active
- disabled
type: string
required:
- status
type: object
models.CustomerUpdateRequest:
properties:
address:
description: 地址,可选
maxLength: 500
type: string
company_size:
description: 企业规模,可选
enum:
- small
- medium
- large
- enterprise
type: string
contact_person:
description: 联系人姓名,可选
maxLength: 100
type: string
contact_title:
description: 联系人职位,可选
maxLength: 100
type: string
customer_level:
description: 客户等级,可选
enum:
- normal
- vip
- enterprise
- strategic
type: string
customer_name:
description: 客户名称,可选
maxLength: 200
type: string
customer_type:
description: 客户类型,可选
enum:
- individual
- enterprise
- government
- education
type: string
description:
description: 描述,可选
maxLength: 1000
type: string
email:
description: 邮箱,可选
maxLength: 255
type: string
phone:
description: 电话,可选
maxLength: 20
type: string
status:
description: 状态,可选
enum:
- active
- disabled
type: string
type: object
models.EnumItem:
properties:
display:
@@ -234,7 +306,7 @@ definitions:
models.LoginResponse:
properties:
code:
type: integer
type: string
data: {}
message:
type: string
@@ -401,6 +473,44 @@ paths:
tags:
- 客户管理
/api/customers/{id}:
delete:
consumes:
- application/json
description: 软删除客户记录
parameters:
- description: 客户ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: 删除成功
schema:
$ref: '#/definitions/models.APIResponse'
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"404":
description: 客户不存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 删除客户
tags:
- 客户管理
get:
consumes:
- application/json
@@ -444,6 +554,105 @@ paths:
summary: 获取客户详情
tags:
- 客户管理
put:
consumes:
- application/json
description: 更新客户信息,所有字段都是可选的
parameters:
- description: 客户ID
in: path
name: id
required: true
type: string
- description: 客户信息
in: body
name: customer
required: true
schema:
$ref: '#/definitions/models.CustomerUpdateRequest'
produces:
- application/json
responses:
"200":
description: 更新成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.Customer'
type: object
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"404":
description: 客户不存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 更新客户信息
tags:
- 客户管理
/api/customers/{id}/status:
patch:
consumes:
- application/json
description: 修改客户状态(激活/停用)
parameters:
- description: 客户ID
in: path
name: id
required: true
type: string
- description: 状态信息
in: body
name: status
required: true
schema:
$ref: '#/definitions/models.CustomerStatusUpdateRequest'
produces:
- application/json
responses:
"200":
description: 状态更新成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.Customer'
type: object
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"404":
description: 客户不存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 修改客户状态
tags:
- 客户管理
/api/enums:
get:
consumes:
+1
View File
@@ -5,6 +5,7 @@ go 1.23
require (
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.4.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.19.0
github.com/swaggo/files v1.0.1
+2
View File
@@ -58,6 +58,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -67,7 +67,7 @@ func (h *AuthHandler) Login(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.LoginResponse{
Code: http.StatusOK,
Code: "000000",
Message: successMessage,
Data: data,
})
@@ -90,7 +90,7 @@ func (h *AuthHandler) Logout(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: http.StatusOK,
Code: "000000",
Message: successMessage,
})
}
@@ -141,7 +141,7 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: http.StatusOK,
Code: "000000",
Message: successMessage,
Data: map[string]interface{}{
"token": newToken,
@@ -51,7 +51,7 @@ func (h *CustomerHandler) GetCustomerList(c *gin.Context) {
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
@@ -86,7 +86,7 @@ func (h *CustomerHandler) GetCustomerList(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: 0,
Code: "000000",
Message: successMessage,
Data: data,
})
@@ -148,7 +148,7 @@ func (h *CustomerHandler) GetCustomer(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: 0,
Code: "000000",
Message: successMessage,
Data: data,
})
@@ -175,7 +175,7 @@ func (h *CustomerHandler) CreateCustomer(c *gin.Context) {
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
@@ -208,9 +208,221 @@ func (h *CustomerHandler) CreateCustomer(c *gin.Context) {
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000001", lang) // 使用客户创建成功的消息
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: 0,
Code: "000000",
Message: successMessage,
Data: data,
})
}
// UpdateCustomer 更新客户信息
// @Summary 更新客户信息
// @Description 更新客户信息,所有字段都是可选的
// @Tags 客户管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "客户ID"
// @Param customer body models.CustomerUpdateRequest true "客户信息"
// @Success 200 {object} models.APIResponse{data=models.Customer} "更新成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "客户不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/customers/{id} [put]
func (h *CustomerHandler) UpdateCustomer(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
var req models.CustomerUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.customerService.UpdateCustomer(ctx, id, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
}
// DeleteCustomer 删除客户
// @Summary 删除客户
// @Description 软删除客户记录
// @Tags 客户管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "客户ID"
// @Success 200 {object} models.APIResponse "删除成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "客户不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/customers/{id} [delete]
func (h *CustomerHandler) DeleteCustomer(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
err := h.customerService.DeleteCustomer(ctx, id)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: nil,
})
}
// UpdateCustomerStatus 修改客户状态
// @Summary 修改客户状态
// @Description 修改客户状态(激活/停用)
// @Tags 客户管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "客户ID"
// @Param status body models.CustomerStatusUpdateRequest true "状态信息"
// @Success 200 {object} models.APIResponse{data=models.Customer} "状态更新成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "客户不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/customers/{id}/status [patch]
func (h *CustomerHandler) UpdateCustomerStatus(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
var req models.CustomerStatusUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.customerService.UpdateCustomerStatus(ctx, id, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
@@ -65,7 +65,7 @@ func (h *EnumHandler) GetAllEnums(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: 0,
Code: "000000",
Message: successMessage,
Data: data,
})
@@ -91,7 +91,7 @@ func (h *EnumHandler) GetEnumsByType(c *gin.Context) {
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Message: message + ": enum type is required",
Timestamp: time.Now().Format(time.RFC3339),
})
return
@@ -126,7 +126,7 @@ func (h *EnumHandler) GetEnumsByType(c *gin.Context) {
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: 0,
Code: "000000",
Message: successMessage,
Data: data,
})
@@ -47,7 +47,7 @@ func (h *SystemHandler) GetSystemInfo(c *gin.Context) {
systemInfo := h.systemService.GetSystemInfo()
c.JSON(http.StatusOK, models.APIResponse{
Code: http.StatusOK,
Code: "000000",
Message: "获取成功",
Data: systemInfo,
})
+3
View File
@@ -76,6 +76,9 @@ func SetupRouter() *gin.Engine {
auth.GET("/customers", customerHandler.GetCustomerList)
auth.GET("/customers/:id", customerHandler.GetCustomer)
auth.POST("/customers", customerHandler.CreateCustomer)
auth.PUT("/customers/:id", customerHandler.UpdateCustomer)
auth.DELETE("/customers/:id", customerHandler.DeleteCustomer)
auth.PATCH("/customers/:id/status", customerHandler.UpdateCustomerStatus)
// 枚举管理
auth.GET("/enums", enumHandler.GetAllEnums)
+1 -1
View File
@@ -8,7 +8,7 @@ type LoginRequest struct {
// LoginResponse 登录响应结构
type LoginResponse struct {
Code int `json:"code"`
Code string `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
+1 -1
View File
@@ -2,7 +2,7 @@ package models
// APIResponse 通用API响应结构
type APIResponse struct {
Code int `json:"code"`
Code string `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
+29 -2
View File
@@ -3,12 +3,13 @@ package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// Customer 客户信息模型
type Customer struct {
ID string `gorm:"type:varchar(36);primaryKey;default:(UUID())" json:"id"`
ID string `gorm:"type:varchar(36);primaryKey" json:"id"`
CustomerCode string `gorm:"type:varchar(20);uniqueIndex;not null" json:"customer_code"`
CustomerName string `gorm:"type:varchar(200);not null;index" json:"customer_name"`
CustomerType string `gorm:"type:varchar(20);not null;default:'enterprise';index" json:"customer_type"`
@@ -43,9 +44,15 @@ func (Customer) TableName() string {
return "customers"
}
// BeforeCreate 创建前自动设置时间戳
// BeforeCreate 创建前自动设置时间戳和ID
// 确保数据完整性,避免依赖数据库默认值(符合失败快速原则)
func (c *Customer) BeforeCreate(tx *gorm.DB) error {
// 生成UUID作为主键ID
if c.ID == "" {
c.ID = uuid.New().String()
}
// 设置时间戳
now := time.Now()
if c.CreatedAt.IsZero() {
c.CreatedAt = now
@@ -119,3 +126,23 @@ type CustomerCreateRequest struct {
Status string `json:"status" binding:"required,oneof=active disabled"` // 状态,必填
Description *string `json:"description" binding:"omitempty,max=1000"` // 描述,可选
}
// CustomerUpdateRequest 更新客户请求结构(所有字段都是可选的)
type CustomerUpdateRequest struct {
CustomerName *string `json:"customer_name" binding:"omitempty,max=200"` // 客户名称,可选
CustomerType *string `json:"customer_type" binding:"omitempty,oneof=individual enterprise government education"` // 客户类型,可选
ContactPerson *string `json:"contact_person" binding:"omitempty,max=100"` // 联系人姓名,可选
ContactTitle *string `json:"contact_title" binding:"omitempty,max=100"` // 联系人职位,可选
Email *string `json:"email" binding:"omitempty,email,max=255"` // 邮箱,可选
Phone *string `json:"phone" binding:"omitempty,max=20"` // 电话,可选
Address *string `json:"address" binding:"omitempty,max=500"` // 地址,可选
CompanySize *string `json:"company_size" binding:"omitempty,oneof=small medium large enterprise"` // 企业规模,可选
CustomerLevel *string `json:"customer_level" binding:"omitempty,oneof=normal vip enterprise strategic"` // 客户等级,可选
Status *string `json:"status" binding:"omitempty,oneof=active disabled"` // 状态,可选
Description *string `json:"description" binding:"omitempty,max=1000"` // 描述,可选
}
// CustomerStatusUpdateRequest 客户状态更新请求结构
type CustomerStatusUpdateRequest struct {
Status string `json:"status" binding:"required,oneof=active disabled"` // 状态,必填
}
@@ -133,6 +133,138 @@ func (s *customerService) CreateCustomer(ctx context.Context, req *models.Custom
return customer, nil
}
// UpdateCustomer 更新客户信息
func (s *customerService) UpdateCustomer(ctx context.Context, id string, req *models.CustomerUpdateRequest) (*models.Customer, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if id == "" {
return nil, i18n.NewI18nError("900001", lang)
}
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
// 先查询现有客户
existingCustomer, err := s.customerRepo.GetCustomerByID(ctx, id)
if err != nil {
if errors.Is(err, repository.ErrCustomerNotFound) {
return nil, i18n.NewI18nError("200001", lang)
}
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 获取当前用户ID
currentUserID := "admin_uuid" // TODO: 从JWT token中获取
// 只更新提供的字段
if req.CustomerName != nil {
existingCustomer.CustomerName = *req.CustomerName
}
if req.CustomerType != nil {
existingCustomer.CustomerType = *req.CustomerType
}
if req.ContactPerson != nil {
existingCustomer.ContactPerson = *req.ContactPerson
}
if req.ContactTitle != nil {
existingCustomer.ContactTitle = req.ContactTitle
}
if req.Email != nil {
existingCustomer.Email = req.Email
}
if req.Phone != nil {
existingCustomer.Phone = req.Phone
}
if req.Address != nil {
existingCustomer.Address = req.Address
}
if req.CompanySize != nil {
existingCustomer.CompanySize = req.CompanySize
}
if req.CustomerLevel != nil {
existingCustomer.CustomerLevel = *req.CustomerLevel
}
if req.Status != nil {
existingCustomer.Status = *req.Status
}
if req.Description != nil {
existingCustomer.Description = req.Description
}
// 设置更新者
existingCustomer.UpdatedBy = &currentUserID
// 委托给Repository层进行数据更新
if err := s.customerRepo.UpdateCustomer(ctx, existingCustomer); err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 填充多语言显示字段
s.fillCustomerDisplayFields(existingCustomer, lang)
return existingCustomer, nil
}
// DeleteCustomer 删除客户
func (s *customerService) DeleteCustomer(ctx context.Context, id string) error {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if id == "" {
return i18n.NewI18nError("900001", lang)
}
// 委托给Repository层进行数据删除
if err := s.customerRepo.DeleteCustomer(ctx, id); err != nil {
if errors.Is(err, repository.ErrCustomerNotFound) {
return i18n.NewI18nError("200001", lang)
}
return i18n.NewI18nError("900004", lang, err.Error())
}
return nil
}
// UpdateCustomerStatus 更新客户状态
func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, req *models.CustomerStatusUpdateRequest) (*models.Customer, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if id == "" {
return nil, i18n.NewI18nError("900001", lang)
}
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
// 先查询现有客户
existingCustomer, err := s.customerRepo.GetCustomerByID(ctx, id)
if err != nil {
if errors.Is(err, repository.ErrCustomerNotFound) {
return nil, i18n.NewI18nError("200001", lang)
}
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 获取当前用户ID
currentUserID := "admin_uuid" // TODO: 从JWT token中获取
// 更新状态
existingCustomer.Status = req.Status
existingCustomer.UpdatedBy = &currentUserID
// 委托给Repository层进行数据更新
if err := s.customerRepo.UpdateCustomer(ctx, existingCustomer); err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 填充多语言显示字段
s.fillCustomerDisplayFields(existingCustomer, lang)
return existingCustomer, nil
}
// fillCustomerDisplayFields 填充完整客户模型的多语言显示字段
func (s *customerService) fillCustomerDisplayFields(customer *models.Customer, lang string) {
customer.CustomerTypeDisplay = i18n.GetEnumMessage("customer_type", customer.CustomerType, lang)
+3
View File
@@ -23,6 +23,9 @@ type CustomerService interface {
GetCustomerList(ctx context.Context, req *models.CustomerListRequest) (*models.CustomerListResponse, error)
GetCustomer(ctx context.Context, id string) (*models.Customer, error)
CreateCustomer(ctx context.Context, req *models.CustomerCreateRequest) (*models.Customer, error)
UpdateCustomer(ctx context.Context, id string, req *models.CustomerUpdateRequest) (*models.Customer, error)
DeleteCustomer(ctx context.Context, id string) error
UpdateCustomerStatus(ctx context.Context, id string, req *models.CustomerStatusUpdateRequest) (*models.Customer, error)
}
// EnumService 枚举服务接口
-83
View File
@@ -1,83 +0,0 @@
package errors
// HTTP 状态码常量
const (
StatusOK = 200
StatusBadRequest = 400
StatusUnauthorized = 401
StatusForbidden = 403
StatusNotFound = 404
StatusConflict = 409
StatusInternalServerError = 500
)
// ErrorMessages 业务错误码到错误信息的映射
// 六位数字格式:前2位模块码 + 后4位错误号
var ErrorMessages = map[string]string{
"000000": "成功",
// 10xxxx - 认证模块
"100001": "认证已过期",
"100002": "认证无效",
"100003": "用户名或密码错误",
"100004": "缺少认证信息",
"100005": "权限不足",
// 20xxxx - 客户模块
"200001": "客户不存在",
"200002": "客户已存在",
"200003": "客户名称不能为空",
"200004": "客户信息格式无效",
"200005": "客户列表查询失败",
// 90xxxx - 系统通用错误
"900001": "请求参数无效",
"900002": "资源不存在",
"900003": "资源冲突",
"900004": "服务器内部错误",
}
// GetErrorMessage 获取错误码对应的错误信息
func GetErrorMessage(code string) string {
if msg, ok := ErrorMessages[code]; ok {
return msg
}
return "未知错误"
}
// NewErrorResponse 创建标准错误响应,返回 HTTP 状态码、错误码、错误消息
// 大部分业务错误返回 200,只有协议级错误才返回对应 HTTP 状态码
func NewErrorResponse(code string, customMessage ...string) (int, string, string) {
httpStatus := getHTTPStatusByCode(code)
message := GetErrorMessage(code)
if len(customMessage) > 0 && customMessage[0] != "" {
message = customMessage[0]
}
return httpStatus, code, message
}
// getHTTPStatusByCode 根据业务错误码获取对应的 HTTP 状态码
func getHTTPStatusByCode(code string) int {
switch code {
case "000000": // 成功
return StatusOK
case "900001": // 请求参数无效
return StatusBadRequest
case "100001", "100002", "100003", "100004": // 认证相关错误
return StatusUnauthorized
case "100005": // 权限不足
return StatusForbidden
case "200001", "900002": // 资源不存在
return StatusNotFound
case "200002", "900003": // 资源冲突
return StatusConflict
case "200003", "200004": // 客户信息无效
return StatusBadRequest
case "200005", "900004": // 服务器内部错误
return StatusOK // 根据设计理念,大部分业务错误返回200,通过响应体区分
default:
return StatusInternalServerError
}
}