1444 lines
53 KiB
Go
1444 lines
53 KiB
Go
package controller
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/QuantumNous/new-api/common"
|
||
"github.com/QuantumNous/new-api/constant"
|
||
"github.com/QuantumNous/new-api/dto"
|
||
"github.com/QuantumNous/new-api/model"
|
||
"github.com/QuantumNous/new-api/service"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// SupplierApplicationSubmitRequest 供应商提交申请请求体。
|
||
type SupplierApplicationSubmitRequest struct {
|
||
ApplicantUserID int `json:"applicant_user_id"`
|
||
SupplierAlias string `json:"supplier_alias"`
|
||
SupplierType string `json:"supplier_type"`
|
||
CompanyName string `json:"company_name"`
|
||
CreditCode string `json:"credit_code"`
|
||
BusinessLicenseURL string `json:"business_license_url"`
|
||
BusinessLicenseFile string `json:"business_license_file"`
|
||
CompanyLogoURL string `json:"company_logo_url"`
|
||
LegalRepresentative string `json:"legal_representative"`
|
||
CompanySize string `json:"company_size"`
|
||
ContactName string `json:"contact_name"`
|
||
ContactMobile string `json:"contact_mobile"`
|
||
ContactWechat string `json:"contact_wechat"`
|
||
}
|
||
|
||
// getSupplierApplicationMissingFieldMessage 返回供应商申请缺失字段的中文提示。
|
||
func getSupplierApplicationMissingFieldMessage(req SupplierApplicationSubmitRequest) string {
|
||
switch {
|
||
case req.CompanyName == "":
|
||
return "请填写企业/主体名称(company_name)"
|
||
case req.CreditCode == "":
|
||
return "请填写统一社会信用代码(credit_code)"
|
||
case req.BusinessLicenseURL == "":
|
||
return "请上传营业执照(business_license_url)"
|
||
case req.CompanyLogoURL == "":
|
||
return "请上传企业Logo(company_logo_url)"
|
||
case req.LegalRepresentative == "":
|
||
return "请填写法人/经营者姓名(legal_representative)"
|
||
case req.ContactName == "":
|
||
return "请填写对接人姓名(contact_name)"
|
||
case req.ContactMobile == "":
|
||
return "请填写对接人手机号(contact_mobile)"
|
||
case req.ContactWechat == "":
|
||
return "请填写对接人微信/企业微信(contact_wechat)"
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
// SupplierApplicationReviewRequest 管理员审核请求体。
|
||
type SupplierApplicationReviewRequest struct {
|
||
Status int `json:"status"`
|
||
Reason string `json:"reason"`
|
||
SupplierAlias string `json:"supplier_alias"`
|
||
SupplierType string `json:"supplier_type"`
|
||
}
|
||
|
||
var allowedSupplierTypes = map[string]struct{}{
|
||
"公有云": {},
|
||
"AIDC": {},
|
||
"企业中转站": {},
|
||
"个人中转站": {},
|
||
}
|
||
|
||
// isValidSupplierType 判断供应商类型是否在允许枚举内。
|
||
func isValidSupplierType(supplierType string) bool {
|
||
_, ok := allowedSupplierTypes[supplierType]
|
||
return ok
|
||
}
|
||
|
||
// SupplierDeactivateRequest 供应商注销请求体。
|
||
type SupplierDeactivateRequest struct {
|
||
SupplierID int `json:"supplier_id"`
|
||
Reason string `json:"reason"`
|
||
}
|
||
|
||
// PublishUserMessageRequest 管理员发布站内消息请求体。
|
||
type PublishUserMessageRequest struct {
|
||
ReceiverUserID int `json:"receiver_user_id"`
|
||
ReceiverMinRole int `json:"receiver_min_role"`
|
||
Type string `json:"type"`
|
||
Title string `json:"title"`
|
||
Content string `json:"content"`
|
||
BizType string `json:"biz_type"`
|
||
BizID int `json:"biz_id"`
|
||
}
|
||
|
||
// SupplierApplicationUpdateRequest 供应商修改申请请求体(必须带申请ID)。
|
||
type SupplierApplicationUpdateRequest struct {
|
||
ID int `json:"id"`
|
||
SupplierApplicationSubmitRequest
|
||
}
|
||
|
||
// SupplierCapabilityUpsertRequest 供应商技术能力档案写入请求体。
|
||
type SupplierCapabilityUpsertRequest struct {
|
||
CoreServiceTypes []string `json:"core_service_types"`
|
||
SupportedModels []string `json:"supported_models"`
|
||
SupportedModelNotes string `json:"supported_model_notes"`
|
||
SupportedAPIEndpoints []string `json:"supported_api_endpoints"`
|
||
SupportedAPIEndpointExtra string `json:"supported_api_endpoint_extra"`
|
||
SupportedParams []string `json:"supported_params"`
|
||
SupportedParamsExtra string `json:"supported_params_extra"`
|
||
StreamingSupported bool `json:"streaming_supported"`
|
||
StreamingNotes string `json:"streaming_notes"`
|
||
StructuredOutputSupported bool `json:"structured_output_supported"`
|
||
StructuredOutputNotes string `json:"structured_output_notes"`
|
||
MultimodalTypes []string `json:"multimodal_types"`
|
||
MultimodalExtra string `json:"multimodal_extra"`
|
||
PricingModes []string `json:"pricing_modes"`
|
||
ReferenceInputPrice string `json:"reference_input_price"`
|
||
ReferenceOutputPrice string `json:"reference_output_price"`
|
||
FailureBillingMode string `json:"failure_billing_mode"`
|
||
FailureBillingNotes string `json:"failure_billing_notes"`
|
||
APIBaseURLs []string `json:"api_base_urls"`
|
||
OpenAICompatible bool `json:"openai_compatible"`
|
||
TruthCommitmentConfirmed bool `json:"truth_commitment_confirmed"`
|
||
}
|
||
|
||
// requireApprovedSupplierApplication 要求当前用户为审核通过供应商。
|
||
func requireApprovedSupplierApplication(c *gin.Context) (*model.SupplierApplication, bool) {
|
||
app, err := model.GetApprovedSupplierApplicationByApplicant(c.GetInt("id"))
|
||
if err != nil {
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusForbidden, gin.H{"success": false, "message": "当前用户未通过供应商审核,无法操作渠道或模型"})
|
||
return nil, false
|
||
}
|
||
common.ApiError(c, err)
|
||
return nil, false
|
||
}
|
||
return app, true
|
||
}
|
||
|
||
// readOptionalStatusQuery 读取可选 status 查询参数。
|
||
func readOptionalStatusQuery(c *gin.Context) (*int, error) {
|
||
statusRaw := strings.TrimSpace(c.Query("status"))
|
||
if statusRaw == "" {
|
||
return nil, nil
|
||
}
|
||
status, err := strconv.Atoi(statusRaw)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if status < model.SupplierApplicationStatusPending || status > model.SupplierApplicationStatusDeactivated {
|
||
return nil, errors.New("invalid status")
|
||
}
|
||
return &status, nil
|
||
}
|
||
|
||
// readSupplierStatusListQuery 读取供应商列表状态查询参数(支持逗号分隔)。
|
||
// 未传时默认返回“审核通过 + 已注销”。
|
||
func readSupplierStatusListQuery(c *gin.Context) ([]int, error) {
|
||
statusRaw := strings.TrimSpace(c.Query("status"))
|
||
if statusRaw == "" {
|
||
return []int{model.SupplierApplicationStatusApproved, model.SupplierApplicationStatusDeactivated}, nil
|
||
}
|
||
statusParts := strings.Split(statusRaw, ",")
|
||
statuses := make([]int, 0, len(statusParts))
|
||
seen := make(map[int]struct{}, len(statusParts))
|
||
for _, part := range statusParts {
|
||
part = strings.TrimSpace(part)
|
||
if part == "" {
|
||
continue
|
||
}
|
||
status, err := strconv.Atoi(part)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if status < model.SupplierApplicationStatusPending || status > model.SupplierApplicationStatusDeactivated {
|
||
return nil, errors.New("invalid status")
|
||
}
|
||
if _, ok := seen[status]; ok {
|
||
continue
|
||
}
|
||
seen[status] = struct{}{}
|
||
statuses = append(statuses, status)
|
||
}
|
||
if len(statuses) == 0 {
|
||
return nil, errors.New("empty status")
|
||
}
|
||
return statuses, nil
|
||
}
|
||
|
||
// trimAndFilterStrings 清理字符串数组中的空白项。
|
||
func trimAndFilterStrings(values []string) []string {
|
||
cleaned := make([]string, 0, len(values))
|
||
for _, item := range values {
|
||
trimmed := strings.TrimSpace(item)
|
||
if trimmed == "" {
|
||
continue
|
||
}
|
||
cleaned = append(cleaned, trimmed)
|
||
}
|
||
return cleaned
|
||
}
|
||
|
||
// marshalStringArray 将字符串数组编码为 JSON 字符串。
|
||
func marshalStringArray(values []string) (string, error) {
|
||
bytes, err := common.Marshal(trimAndFilterStrings(values))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return string(bytes), nil
|
||
}
|
||
|
||
// unmarshalStringArray 将 JSON 字符串解码为字符串数组。
|
||
func unmarshalStringArray(raw string) []string {
|
||
raw = strings.TrimSpace(raw)
|
||
if raw == "" {
|
||
return []string{}
|
||
}
|
||
var items []string
|
||
if err := common.UnmarshalJsonStr(raw, &items); err != nil {
|
||
return []string{}
|
||
}
|
||
return trimAndFilterStrings(items)
|
||
}
|
||
|
||
// buildSupplierCapabilityModel 将请求体转换为模型对象。
|
||
func buildSupplierCapabilityModel(req *SupplierCapabilityUpsertRequest) (*model.SupplierCapability, error) {
|
||
coreServiceTypes, err := marshalStringArray(req.CoreServiceTypes)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
supportedModels, err := marshalStringArray(req.SupportedModels)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
supportedAPIEndpoints, err := marshalStringArray(req.SupportedAPIEndpoints)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
supportedParams, err := marshalStringArray(req.SupportedParams)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
multimodalTypes, err := marshalStringArray(req.MultimodalTypes)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
pricingModes, err := marshalStringArray(req.PricingModes)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
apiBaseURLs, err := marshalStringArray(req.APIBaseURLs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &model.SupplierCapability{
|
||
CoreServiceTypes: coreServiceTypes,
|
||
SupportedModels: supportedModels,
|
||
SupportedModelNotes: strings.TrimSpace(req.SupportedModelNotes),
|
||
SupportedAPIEndpoints: supportedAPIEndpoints,
|
||
SupportedAPIEndpointExtra: strings.TrimSpace(req.SupportedAPIEndpointExtra),
|
||
SupportedParams: supportedParams,
|
||
SupportedParamsExtra: strings.TrimSpace(req.SupportedParamsExtra),
|
||
StreamingSupported: req.StreamingSupported,
|
||
StreamingNotes: strings.TrimSpace(req.StreamingNotes),
|
||
StructuredOutputSupported: req.StructuredOutputSupported,
|
||
StructuredOutputNotes: strings.TrimSpace(req.StructuredOutputNotes),
|
||
MultimodalTypes: multimodalTypes,
|
||
MultimodalExtra: strings.TrimSpace(req.MultimodalExtra),
|
||
PricingModes: pricingModes,
|
||
ReferenceInputPrice: strings.TrimSpace(req.ReferenceInputPrice),
|
||
ReferenceOutputPrice: strings.TrimSpace(req.ReferenceOutputPrice),
|
||
FailureBillingMode: strings.TrimSpace(req.FailureBillingMode),
|
||
FailureBillingNotes: strings.TrimSpace(req.FailureBillingNotes),
|
||
APIBaseURLs: apiBaseURLs,
|
||
OpenAICompatible: req.OpenAICompatible,
|
||
TruthCommitmentConfirmed: req.TruthCommitmentConfirmed,
|
||
}, nil
|
||
}
|
||
|
||
// buildSupplierCapabilityResponse 将模型对象转换为接口响应对象。
|
||
func buildSupplierCapabilityResponse(capability *model.SupplierCapability) gin.H {
|
||
if capability == nil {
|
||
return nil
|
||
}
|
||
return gin.H{
|
||
"id": capability.ID,
|
||
"supplier_application_id": capability.SupplierApplicationID,
|
||
"core_service_types": unmarshalStringArray(capability.CoreServiceTypes),
|
||
"supported_models": unmarshalStringArray(capability.SupportedModels),
|
||
"supported_model_notes": capability.SupportedModelNotes,
|
||
"supported_api_endpoints": unmarshalStringArray(capability.SupportedAPIEndpoints),
|
||
"supported_api_endpoint_extra": capability.SupportedAPIEndpointExtra,
|
||
"supported_params": unmarshalStringArray(capability.SupportedParams),
|
||
"supported_params_extra": capability.SupportedParamsExtra,
|
||
"streaming_supported": capability.StreamingSupported,
|
||
"streaming_notes": capability.StreamingNotes,
|
||
"structured_output_supported": capability.StructuredOutputSupported,
|
||
"structured_output_notes": capability.StructuredOutputNotes,
|
||
"multimodal_types": unmarshalStringArray(capability.MultimodalTypes),
|
||
"multimodal_extra": capability.MultimodalExtra,
|
||
"pricing_modes": unmarshalStringArray(capability.PricingModes),
|
||
"reference_input_price": capability.ReferenceInputPrice,
|
||
"reference_output_price": capability.ReferenceOutputPrice,
|
||
"failure_billing_mode": capability.FailureBillingMode,
|
||
"failure_billing_notes": capability.FailureBillingNotes,
|
||
"api_base_urls": unmarshalStringArray(capability.APIBaseURLs),
|
||
"openai_compatible": capability.OpenAICompatible,
|
||
"truth_commitment_confirmed": capability.TruthCommitmentConfirmed,
|
||
"created_at": capability.CreatedAt,
|
||
"updated_at": capability.UpdatedAt,
|
||
}
|
||
}
|
||
|
||
// attachSupplierCapability 为供应商申请对象补充技术能力档案。
|
||
func attachSupplierCapability(app *model.SupplierApplication) error {
|
||
if app == nil {
|
||
return nil
|
||
}
|
||
capability, err := model.GetSupplierCapabilityByApplicationID(app.ID)
|
||
if err != nil {
|
||
if model.IsSupplierCapabilityNotFound(err) {
|
||
app.SupplierCapability = nil
|
||
return nil
|
||
}
|
||
return err
|
||
}
|
||
app.SupplierCapability = capability
|
||
return nil
|
||
}
|
||
|
||
// SubmitSupplierApplication godoc
|
||
// @Summary 提交供应商入驻申请
|
||
// @Description 普通用户提交供应商申请,提交后生成管理员待审核站内消息
|
||
// @Tags Supplier
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body SupplierApplicationSubmitRequest true "申请信息"
|
||
// @Success 200 {object} map[string]interface{} "success + data{id,status}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/application [post]
|
||
func SubmitSupplierApplication(c *gin.Context) {
|
||
var req SupplierApplicationSubmitRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
req.CompanyName = strings.TrimSpace(req.CompanyName)
|
||
req.CreditCode = strings.TrimSpace(req.CreditCode)
|
||
req.BusinessLicenseURL = strings.TrimSpace(req.BusinessLicenseURL)
|
||
req.BusinessLicenseFile = strings.TrimSpace(req.BusinessLicenseFile)
|
||
req.CompanyLogoURL = strings.TrimSpace(req.CompanyLogoURL)
|
||
req.SupplierType = strings.TrimSpace(req.SupplierType)
|
||
req.LegalRepresentative = strings.TrimSpace(req.LegalRepresentative)
|
||
req.CompanySize = strings.TrimSpace(req.CompanySize)
|
||
req.ContactName = strings.TrimSpace(req.ContactName)
|
||
req.ContactMobile = strings.TrimSpace(req.ContactMobile)
|
||
req.ContactWechat = strings.TrimSpace(req.ContactWechat)
|
||
if missingFieldMessage := getSupplierApplicationMissingFieldMessage(req); missingFieldMessage != "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": missingFieldMessage})
|
||
return
|
||
}
|
||
if len(req.CreditCode) != 18 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "统一社会信用代码需为18位"})
|
||
return
|
||
}
|
||
|
||
isAdminOrAbove := c.GetInt("role") >= common.RoleAdminUser
|
||
applicantUserID := c.GetInt("id")
|
||
if isAdminOrAbove {
|
||
if req.SupplierType == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "管理员代添加供应商时必须选择供应商类型"})
|
||
return
|
||
}
|
||
if !isValidSupplierType(req.SupplierType) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商类型"})
|
||
return
|
||
}
|
||
if req.ApplicantUserID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "管理员代添加供应商时必须提供有效的applicant_user_id"})
|
||
return
|
||
}
|
||
if _, err := model.GetUserById(req.ApplicantUserID, false); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "指定的关联用户不存在"})
|
||
return
|
||
}
|
||
applicantUserID = req.ApplicantUserID
|
||
}
|
||
app := &model.SupplierApplication{
|
||
ApplicantUserID: applicantUserID,
|
||
CompanyName: req.CompanyName,
|
||
CreditCode: req.CreditCode,
|
||
BusinessLicenseURL: req.BusinessLicenseURL,
|
||
BusinessLicenseFile: req.BusinessLicenseFile,
|
||
CompanyLogoURL: req.CompanyLogoURL,
|
||
SupplierType: req.SupplierType,
|
||
LegalRepresentative: req.LegalRepresentative,
|
||
CompanySize: req.CompanySize,
|
||
ContactName: req.ContactName,
|
||
ContactMobile: req.ContactMobile,
|
||
ContactWechat: req.ContactWechat,
|
||
}
|
||
var err error
|
||
if isAdminOrAbove {
|
||
err = model.CreateSupplierApplicationAutoApproved(app, c.GetInt("id"))
|
||
} else {
|
||
app.Status = model.SupplierApplicationStatusPending
|
||
err = model.CreateSupplierApplication(app)
|
||
}
|
||
if err != nil {
|
||
if model.IsSupplierCreditCodeDuplicateError(err) {
|
||
common.ApiErrorMsg(c, "统一社会信用代码已存在,请核对后重试")
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if !isAdminOrAbove {
|
||
_ = model.CreateSupplierApplicationAudit(&model.SupplierApplicationAudit{
|
||
ApplicationID: app.ID,
|
||
OperatorUserID: app.ApplicantUserID,
|
||
Action: model.SupplierApplicationAuditActionSubmit,
|
||
FromStatus: model.SupplierApplicationStatusPending,
|
||
ToStatus: model.SupplierApplicationStatusPending,
|
||
Reason: "",
|
||
})
|
||
_ = service.PublishUserMessage(&model.UserMessage{
|
||
ReceiverUserID: 0,
|
||
ReceiverMinRole: common.RoleAdminUser,
|
||
Type: model.UserMessageTypeSupplierSubmitted,
|
||
Title: "供应商入驻待审核",
|
||
Content: fmt.Sprintf("收到新的供应商申请:%s(统一社会信用代码:%s)", app.CompanyName, app.CreditCode),
|
||
BizType: model.UserMessageBizTypeSupplierApplication,
|
||
BizID: app.ID,
|
||
})
|
||
}
|
||
common.ApiSuccess(c, gin.H{
|
||
"id": app.ID,
|
||
"status": app.Status,
|
||
})
|
||
}
|
||
|
||
// GetMySupplierApplication godoc
|
||
// @Summary 查询当前用户供应商申请
|
||
// @Tags Supplier
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Success 200 {object} map[string]interface{} "success + data{申请对象或null}"
|
||
// @Router /user/supplier/application/self [get]
|
||
func GetMySupplierApplication(c *gin.Context) {
|
||
app, err := model.GetMySupplierApplication(c.GetInt("id"))
|
||
if err != nil {
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
common.ApiSuccess(c, nil)
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if err = attachSupplierCapability(app); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
response := gin.H{
|
||
"id": app.ID,
|
||
"applicant_user_id": app.ApplicantUserID,
|
||
"company_name": app.CompanyName,
|
||
"credit_code": app.CreditCode,
|
||
"business_license_url": app.BusinessLicenseURL,
|
||
"business_license_file": app.BusinessLicenseFile,
|
||
"company_logo_url": app.CompanyLogoURL,
|
||
"supplier_type": app.SupplierType,
|
||
"legal_representative": app.LegalRepresentative,
|
||
"company_size": app.CompanySize,
|
||
"contact_name": app.ContactName,
|
||
"contact_mobile": app.ContactMobile,
|
||
"contact_wechat": app.ContactWechat,
|
||
"supplier_alias": app.SupplierAlias,
|
||
"status": app.Status,
|
||
"review_reason": app.ReviewReason,
|
||
"reviewed_by": app.ReviewedBy,
|
||
"reviewed_at": app.ReviewedAt,
|
||
"created_at": app.CreatedAt,
|
||
"updated_at": app.UpdatedAt,
|
||
"supplier_capability": buildSupplierCapabilityResponse(app.SupplierCapability),
|
||
}
|
||
common.ApiSuccess(c, response)
|
||
}
|
||
|
||
// GetSupplierCapability godoc
|
||
// @Summary 查询供应商技术能力档案
|
||
// @Description 管理员可查任意申请;普通用户仅可查询自己的申请
|
||
// @Tags Supplier
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param id path int true "供应商申请ID"
|
||
// @Success 200 {object} map[string]interface{} "success + data{供应商技术能力档案}"
|
||
// @Router /user/supplier/application/{id}/capability [get]
|
||
func GetSupplierCapability(c *gin.Context) {
|
||
applicationID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil || applicationID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商申请ID"})
|
||
return
|
||
}
|
||
app, err := model.GetSupplierByID(applicationID)
|
||
if err != nil {
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到供应商申请"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if c.GetInt("role") < common.RoleAdminUser && app.ApplicantUserID != c.GetInt("id") {
|
||
c.JSON(http.StatusForbidden, gin.H{"success": false, "message": "无权查看该供应商技术能力档案"})
|
||
return
|
||
}
|
||
capability, err := model.GetSupplierCapabilityByApplicationID(applicationID)
|
||
if err != nil {
|
||
if model.IsSupplierCapabilityNotFound(err) {
|
||
common.ApiSuccess(c, nil)
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.ApiSuccess(c, buildSupplierCapabilityResponse(capability))
|
||
}
|
||
|
||
// UpsertSupplierCapability godoc
|
||
// @Summary 保存供应商技术能力档案
|
||
// @Description 管理员可保存任意申请;普通用户仅可保存自己的申请
|
||
// @Tags Supplier
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param id path int true "供应商申请ID"
|
||
// @Param request body SupplierCapabilityUpsertRequest true "供应商技术能力档案"
|
||
// @Success 200 {object} map[string]interface{} "success + data{供应商技术能力档案}"
|
||
// @Router /user/supplier/application/{id}/capability [put]
|
||
func UpsertSupplierCapability(c *gin.Context) {
|
||
applicationID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil || applicationID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商申请ID"})
|
||
return
|
||
}
|
||
app, err := model.GetSupplierByID(applicationID)
|
||
if err != nil {
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到供应商申请"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if c.GetInt("role") < common.RoleAdminUser && app.ApplicantUserID != c.GetInt("id") {
|
||
c.JSON(http.StatusForbidden, gin.H{"success": false, "message": "无权修改该供应商技术能力档案"})
|
||
return
|
||
}
|
||
var req SupplierCapabilityUpsertRequest
|
||
if err = c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
capabilityModel, err := buildSupplierCapabilityModel(&req)
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
updated, err := model.UpsertSupplierCapabilityByApplicationID(applicationID, capabilityModel)
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.ApiSuccess(c, buildSupplierCapabilityResponse(updated))
|
||
}
|
||
|
||
// UpdateMySupplierApplication godoc
|
||
// @Summary 修改当前用户供应商申请并重新提交
|
||
// @Description 当前申请只要未审核通过都可修改,修改后状态重置为待审核(0)
|
||
// @Tags Supplier
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body SupplierApplicationUpdateRequest true "申请信息(含id)"
|
||
// @Success 200 {object} map[string]interface{} "success + data{id,status}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/application/self [put]
|
||
func UpdateMySupplierApplication(c *gin.Context) {
|
||
var req SupplierApplicationUpdateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
if req.ID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "修改申请时必须提供有效的id"})
|
||
return
|
||
}
|
||
req.CompanyName = strings.TrimSpace(req.CompanyName)
|
||
req.CreditCode = strings.TrimSpace(req.CreditCode)
|
||
req.BusinessLicenseURL = strings.TrimSpace(req.BusinessLicenseURL)
|
||
req.BusinessLicenseFile = strings.TrimSpace(req.BusinessLicenseFile)
|
||
req.CompanyLogoURL = strings.TrimSpace(req.CompanyLogoURL)
|
||
req.SupplierType = strings.TrimSpace(req.SupplierType)
|
||
req.LegalRepresentative = strings.TrimSpace(req.LegalRepresentative)
|
||
req.CompanySize = strings.TrimSpace(req.CompanySize)
|
||
req.ContactName = strings.TrimSpace(req.ContactName)
|
||
req.ContactMobile = strings.TrimSpace(req.ContactMobile)
|
||
req.ContactWechat = strings.TrimSpace(req.ContactWechat)
|
||
if req.CompanyName == "" || req.CreditCode == "" || req.BusinessLicenseURL == "" || req.CompanyLogoURL == "" ||
|
||
req.LegalRepresentative == "" || req.ContactName == "" || req.ContactMobile == "" || req.ContactWechat == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请填写完整的必填字段"})
|
||
return
|
||
}
|
||
if len(req.CreditCode) != 18 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "统一社会信用代码需为18位"})
|
||
return
|
||
}
|
||
app, err := model.UpdateMySupplierApplication(c.GetInt("id"), req.ID, &model.SupplierApplication{
|
||
CompanyName: req.CompanyName,
|
||
CreditCode: req.CreditCode,
|
||
BusinessLicenseURL: req.BusinessLicenseURL,
|
||
BusinessLicenseFile: req.BusinessLicenseFile,
|
||
CompanyLogoURL: req.CompanyLogoURL,
|
||
SupplierType: req.SupplierType,
|
||
LegalRepresentative: req.LegalRepresentative,
|
||
CompanySize: req.CompanySize,
|
||
ContactName: req.ContactName,
|
||
ContactMobile: req.ContactMobile,
|
||
ContactWechat: req.ContactWechat,
|
||
})
|
||
if err != nil {
|
||
if model.IsSupplierCreditCodeDuplicateError(err) {
|
||
common.ApiErrorMsg(c, "统一社会信用代码已存在,请核对后重试")
|
||
return
|
||
}
|
||
if errors.Is(err, model.ErrSupplierApplicationStatusNotEditable) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "当前申请状态不可修改"})
|
||
return
|
||
}
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到可修改的供应商申请"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
_ = service.PublishUserMessage(&model.UserMessage{
|
||
ReceiverUserID: 0,
|
||
ReceiverMinRole: common.RoleAdminUser,
|
||
Type: model.UserMessageTypeSupplierSubmitted,
|
||
Title: "供应商入驻待审核",
|
||
Content: fmt.Sprintf("供应商申请已更新并重新提交:%s(统一社会信用代码:%s)", app.CompanyName, app.CreditCode),
|
||
BizType: model.UserMessageBizTypeSupplierApplication,
|
||
BizID: app.ID,
|
||
})
|
||
common.ApiSuccess(c, gin.H{
|
||
"id": app.ID,
|
||
"status": app.Status,
|
||
})
|
||
}
|
||
|
||
// AdminListSupplierApplications godoc
|
||
// @Summary 管理员分页查询供应商申请
|
||
// @Tags SupplierAdmin
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param p query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Param status query int false "状态:0待审核 1审核通过 2审核驳回"
|
||
// @Success 200 {object} map[string]interface{} "分页结果"
|
||
// @Router /user/supplier/application [get]
|
||
func AdminListSupplierApplications(c *gin.Context) {
|
||
status, err := readOptionalStatusQuery(c)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的status参数"})
|
||
return
|
||
}
|
||
pageInfo := common.GetPageQuery(c)
|
||
items, total, err := model.ListSupplierApplications(status, pageInfo)
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
pageInfo.SetTotal(int(total))
|
||
pageInfo.SetItems(items)
|
||
common.ApiSuccess(c, pageInfo)
|
||
}
|
||
|
||
// AdminListSuppliers godoc
|
||
// @Summary 管理员分页查询供应商列表
|
||
// @Description 支持按供应商名称模糊查询,返回分页数据
|
||
// @Tags SupplierAdmin
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param p query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Param company_name query string false "供应商名称(模糊)"
|
||
// @Param status query string false "状态筛选,支持逗号分隔(如1,3);默认查询1和3"
|
||
// @Success 200 {object} map[string]interface{} "分页结果"
|
||
// @Router /user/supplier/list [get]
|
||
func AdminListSuppliers(c *gin.Context) {
|
||
pageInfo := common.GetPageQuery(c)
|
||
companyName := strings.TrimSpace(c.Query("company_name"))
|
||
statuses, err := readSupplierStatusListQuery(c)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的status参数"})
|
||
return
|
||
}
|
||
items, total, err := model.ListSuppliersByCompanyName(companyName, statuses, pageInfo)
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
pageInfo.SetTotal(int(total))
|
||
pageInfo.SetItems(items)
|
||
common.ApiSuccess(c, pageInfo)
|
||
}
|
||
|
||
// AdminGetSupplierDetail godoc
|
||
// @Summary 管理员查询供应商详情
|
||
// @Description 根据供应商ID查询供应商详情,返回申请人用户名 applicant_username
|
||
// @Tags SupplierAdmin
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param id path int true "供应商ID"
|
||
// @Success 200 {object} map[string]interface{} "供应商详情"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/{id} [get]
|
||
func AdminGetSupplierDetail(c *gin.Context) {
|
||
supplierID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil || supplierID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商ID"})
|
||
return
|
||
}
|
||
item, err := model.GetSupplierByID(supplierID)
|
||
if err != nil {
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到供应商信息"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if err = attachSupplierCapability(item); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
response := gin.H{
|
||
"id": item.ID,
|
||
"applicant_user_id": item.ApplicantUserID,
|
||
"applicant_username": item.ApplicantUsername,
|
||
"company_name": item.CompanyName,
|
||
"credit_code": item.CreditCode,
|
||
"business_license_url": item.BusinessLicenseURL,
|
||
"business_license_file": item.BusinessLicenseFile,
|
||
"company_logo_url": item.CompanyLogoURL,
|
||
"supplier_type": item.SupplierType,
|
||
"legal_representative": item.LegalRepresentative,
|
||
"company_size": item.CompanySize,
|
||
"contact_name": item.ContactName,
|
||
"contact_mobile": item.ContactMobile,
|
||
"contact_wechat": item.ContactWechat,
|
||
"supplier_alias": item.SupplierAlias,
|
||
"status": item.Status,
|
||
"review_reason": item.ReviewReason,
|
||
"reviewed_by": item.ReviewedBy,
|
||
"reviewed_at": item.ReviewedAt,
|
||
"created_at": item.CreatedAt,
|
||
"updated_at": item.UpdatedAt,
|
||
"supplier_capability": buildSupplierCapabilityResponse(item.SupplierCapability),
|
||
}
|
||
common.ApiSuccess(c, response)
|
||
}
|
||
|
||
// AdminUpdateSupplierApplication godoc
|
||
// @Summary 管理员修改供应商申请资料
|
||
// @Description 管理员可修改任意供应商申请资料;审核通过(status=1)状态也允许修改,且修改后保持原状态
|
||
// @Tags SupplierAdmin
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param id path int true "供应商申请ID"
|
||
// @Param request body SupplierApplicationSubmitRequest true "申请信息"
|
||
// @Success 200 {object} map[string]interface{} "success + data{id,status}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/application/{id} [put]
|
||
func AdminUpdateSupplierApplication(c *gin.Context) {
|
||
applicationID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil || applicationID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商申请ID"})
|
||
return
|
||
}
|
||
var req SupplierApplicationSubmitRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
req.CompanyName = strings.TrimSpace(req.CompanyName)
|
||
req.CreditCode = strings.TrimSpace(req.CreditCode)
|
||
req.BusinessLicenseURL = strings.TrimSpace(req.BusinessLicenseURL)
|
||
req.BusinessLicenseFile = strings.TrimSpace(req.BusinessLicenseFile)
|
||
req.CompanyLogoURL = strings.TrimSpace(req.CompanyLogoURL)
|
||
req.SupplierType = strings.TrimSpace(req.SupplierType)
|
||
req.LegalRepresentative = strings.TrimSpace(req.LegalRepresentative)
|
||
req.CompanySize = strings.TrimSpace(req.CompanySize)
|
||
req.ContactName = strings.TrimSpace(req.ContactName)
|
||
req.ContactMobile = strings.TrimSpace(req.ContactMobile)
|
||
req.ContactWechat = strings.TrimSpace(req.ContactWechat)
|
||
req.SupplierAlias = strings.TrimSpace(req.SupplierAlias)
|
||
if req.CompanyName == "" || req.CreditCode == "" || req.BusinessLicenseURL == "" || req.CompanyLogoURL == "" ||
|
||
req.LegalRepresentative == "" || req.ContactName == "" || req.ContactMobile == "" || req.ContactWechat == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请填写完整的必填字段"})
|
||
return
|
||
}
|
||
if req.SupplierType == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请填写供应商类型"})
|
||
return
|
||
}
|
||
if !isValidSupplierType(req.SupplierType) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商类型"})
|
||
return
|
||
}
|
||
if len(req.CreditCode) != 18 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "统一社会信用代码需为18位"})
|
||
return
|
||
}
|
||
aliasPtr := req.SupplierAlias
|
||
app, err := model.AdminUpdateSupplierApplication(applicationID, &model.SupplierApplication{
|
||
CompanyName: req.CompanyName,
|
||
CreditCode: req.CreditCode,
|
||
BusinessLicenseURL: req.BusinessLicenseURL,
|
||
BusinessLicenseFile: req.BusinessLicenseFile,
|
||
CompanyLogoURL: req.CompanyLogoURL,
|
||
SupplierType: req.SupplierType,
|
||
LegalRepresentative: req.LegalRepresentative,
|
||
CompanySize: req.CompanySize,
|
||
ContactName: req.ContactName,
|
||
ContactMobile: req.ContactMobile,
|
||
ContactWechat: req.ContactWechat,
|
||
SupplierAlias: &aliasPtr,
|
||
})
|
||
if err != nil {
|
||
if model.IsSupplierCreditCodeDuplicateError(err) {
|
||
common.ApiErrorMsg(c, "统一社会信用代码已存在,请核对后重试")
|
||
return
|
||
}
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到可修改的供应商申请"})
|
||
return
|
||
}
|
||
if model.IsSupplierAliasDuplicateError(err) {
|
||
common.ApiErrorMsg(c, "供应商别名已存在,请更换后重试")
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.ApiSuccess(c, gin.H{
|
||
"id": app.ID,
|
||
"status": app.Status,
|
||
})
|
||
}
|
||
|
||
// AdminReviewSupplierApplication godoc
|
||
// @Summary 管理员审核供应商申请
|
||
// @Description 任一管理员可审核一次,仅待审核状态允许处理
|
||
// @Tags SupplierAdmin
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param id path int true "申请ID"
|
||
// @Param request body SupplierApplicationReviewRequest true "审核信息"
|
||
// @Success 200 {object} map[string]interface{} "success + data{id,status}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/application/{id}/review [post]
|
||
func AdminReviewSupplierApplication(c *gin.Context) {
|
||
applicationID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil || applicationID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的申请ID"})
|
||
return
|
||
}
|
||
var req SupplierApplicationReviewRequest
|
||
if err = c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
if req.Status != model.SupplierApplicationStatusApproved && req.Status != model.SupplierApplicationStatusRejected {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "审核状态仅支持通过或驳回"})
|
||
return
|
||
}
|
||
req.Reason = strings.TrimSpace(req.Reason)
|
||
req.SupplierType = strings.TrimSpace(req.SupplierType)
|
||
if req.Status == model.SupplierApplicationStatusRejected && req.Reason == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "驳回时请填写原因"})
|
||
return
|
||
}
|
||
if req.Status == model.SupplierApplicationStatusApproved {
|
||
capability, capabilityErr := model.GetSupplierCapabilityByApplicationID(applicationID)
|
||
if capabilityErr != nil {
|
||
if model.IsSupplierCapabilityNotFound(capabilityErr) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "审批通过前请先完善供应商技术能力信息"})
|
||
return
|
||
}
|
||
common.ApiError(c, capabilityErr)
|
||
return
|
||
}
|
||
if !model.IsSupplierCapabilityComplete(capability) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "审批通过前请先完善供应商技术能力必填字段"})
|
||
return
|
||
}
|
||
}
|
||
|
||
req.SupplierAlias = strings.TrimSpace(req.SupplierAlias)
|
||
if req.Status == model.SupplierApplicationStatusApproved {
|
||
if req.SupplierType == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "审批通过时必须选择供应商类型"})
|
||
return
|
||
}
|
||
if !isValidSupplierType(req.SupplierType) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的供应商类型"})
|
||
return
|
||
}
|
||
}
|
||
app, err := model.ReviewSupplierApplication(applicationID, c.GetInt("id"), req.Status, req.Reason, req.SupplierAlias, req.SupplierType)
|
||
if err != nil {
|
||
if errors.Is(err, model.ErrSupplierApplicationAlreadyReviewed) {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "该申请已被其他管理员处理"})
|
||
return
|
||
}
|
||
if model.IsSupplierAliasDuplicateError(err) {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "供应商别名已存在,请更换后重试"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
|
||
msgType := model.UserMessageTypeSupplierApproved
|
||
msgTitle := "供应商入驻审核通过"
|
||
msgContent := fmt.Sprintf("你的供应商申请“%s”已审核通过。", app.CompanyName)
|
||
if app.Status == model.SupplierApplicationStatusRejected {
|
||
msgType = model.UserMessageTypeSupplierRejected
|
||
msgTitle = "供应商入驻审核驳回"
|
||
msgContent = fmt.Sprintf("你的供应商申请“%s”已驳回,原因:%s", app.CompanyName, req.Reason)
|
||
}
|
||
_ = service.PublishUserMessage(&model.UserMessage{
|
||
ReceiverUserID: app.ApplicantUserID,
|
||
ReceiverMinRole: 0,
|
||
Type: msgType,
|
||
Title: msgTitle,
|
||
Content: msgContent,
|
||
BizType: model.UserMessageBizTypeSupplierApplication,
|
||
BizID: app.ID,
|
||
})
|
||
common.ApiSuccess(c, gin.H{
|
||
"id": app.ID,
|
||
"status": app.Status,
|
||
})
|
||
}
|
||
|
||
// ListMyMessages godoc
|
||
// @Summary 查询当前用户站内消息
|
||
// @Tags Message
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param p query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Param title query string false "标题模糊查询"
|
||
// @Param read_status query string false "读取状态:all/read/unread,默认all"
|
||
// @Success 200 {object} map[string]interface{} "分页结果"
|
||
// @Router /user/messages/self [get]
|
||
func ListMyMessages(c *gin.Context) {
|
||
pageInfo := common.GetPageQuery(c)
|
||
userID := c.GetInt("id")
|
||
role := c.GetInt("role")
|
||
titleKeyword := strings.TrimSpace(c.Query("title"))
|
||
readStatus := strings.TrimSpace(c.Query("read_status"))
|
||
if readStatus == "" {
|
||
readStatus = "all"
|
||
}
|
||
if readStatus != "all" && readStatus != "read" && readStatus != "unread" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的read_status参数"})
|
||
return
|
||
}
|
||
items, total, err := model.ListUserMessagesForUser(userID, role, pageInfo, titleKeyword, readStatus)
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
pageInfo.SetTotal(int(total))
|
||
pageInfo.SetItems(items)
|
||
common.ApiSuccess(c, pageInfo)
|
||
}
|
||
|
||
// MarkMyMessageRead godoc
|
||
// @Summary 标记当前用户消息为已读
|
||
// @Tags Message
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param id path int true "消息ID"
|
||
// @Success 200 {object} map[string]interface{} "success + data{updated}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/messages/{id}/read [post]
|
||
func MarkMyMessageRead(c *gin.Context) {
|
||
messageID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil || messageID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的消息ID"})
|
||
return
|
||
}
|
||
ok, err := model.MarkUserMessageAsRead(messageID, c.GetInt("id"), c.GetInt("role"))
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.ApiSuccess(c, gin.H{"updated": ok})
|
||
}
|
||
|
||
// MarkAllMyMessagesRead godoc
|
||
// @Summary 标记当前用户全部站内消息为已读
|
||
// @Tags Message
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Success 200 {object} map[string]interface{} "success + data{updated_count}"
|
||
// @Router /user/messages/read_all [post]
|
||
func MarkAllMyMessagesRead(c *gin.Context) {
|
||
updatedCount, err := model.MarkAllUserMessagesAsRead(c.GetInt("id"), c.GetInt("role"))
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.ApiSuccess(c, gin.H{"updated_count": updatedCount})
|
||
}
|
||
|
||
// AdminPublishUserMessage godoc
|
||
// @Summary 管理员发布站内消息
|
||
// @Description 支持按指定用户或按最小角色发布站内消息,至少设置 receiver_user_id 或 receiver_min_role 之一
|
||
// @Tags MessageAdmin
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body PublishUserMessageRequest true "消息内容"
|
||
// @Success 200 {object} map[string]interface{} "success + data{published:true}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/messages/publish [post]
|
||
func AdminPublishUserMessage(c *gin.Context) {
|
||
var req PublishUserMessageRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
if req.ReceiverUserID <= 0 && req.ReceiverMinRole <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请至少指定接收用户或角色门槛"})
|
||
return
|
||
}
|
||
msg := &model.UserMessage{
|
||
ReceiverUserID: req.ReceiverUserID,
|
||
ReceiverMinRole: req.ReceiverMinRole,
|
||
Type: req.Type,
|
||
Title: req.Title,
|
||
Content: req.Content,
|
||
BizType: req.BizType,
|
||
BizID: req.BizID,
|
||
}
|
||
if err := service.PublishUserMessage(msg); err != nil {
|
||
common.ApiErrorMsg(c, err.Error())
|
||
return
|
||
}
|
||
common.ApiSuccess(c, gin.H{"published": true})
|
||
}
|
||
|
||
// GetMyUnreadMessageCount godoc
|
||
// @Summary 获取当前用户未读站内消息数量
|
||
// @Tags Message
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Success 200 {object} map[string]interface{} "success + data{unread_count}"
|
||
// @Router /user/messages/unread_count [get]
|
||
func GetMyUnreadMessageCount(c *gin.Context) {
|
||
total, err := model.CountUnreadUserMessages(c.GetInt("id"), c.GetInt("role"))
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.ApiSuccess(c, gin.H{"unread_count": total})
|
||
}
|
||
|
||
// DeactivateMySupplierApplication godoc
|
||
// @Summary 当前供应商注销
|
||
// @Description 仅审核通过状态可注销;注销后清空用户表 supplier_id 并将申请状态置为已注销
|
||
// @Tags Supplier
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body SupplierDeactivateRequest false "注销说明"
|
||
// @Success 200 {object} map[string]interface{} "success + data{id,status}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/application/deactivate [post]
|
||
func DeactivateMySupplierApplication(c *gin.Context) {
|
||
var req SupplierDeactivateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
if req.SupplierID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "注销供应商时必须提供有效的supplier_id"})
|
||
return
|
||
}
|
||
reason := strings.TrimSpace(req.Reason)
|
||
app, err := model.DeactivateSupplierApplication(c.GetInt("id"), c.GetInt("role"), req.SupplierID, reason)
|
||
if err != nil {
|
||
if errors.Is(err, model.ErrSupplierApplicationStatusNotApproved) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "当前供应商状态不支持注销"})
|
||
return
|
||
}
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到可注销的供应商申请"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
_ = service.PublishUserMessage(&model.UserMessage{
|
||
ReceiverUserID: 0,
|
||
ReceiverMinRole: common.RoleAdminUser,
|
||
Type: model.UserMessageTypeSupplierRejected,
|
||
Title: "供应商已注销",
|
||
Content: fmt.Sprintf("供应商“%s”已注销。", app.CompanyName),
|
||
BizType: model.UserMessageBizTypeSupplierApplication,
|
||
BizID: app.ID,
|
||
})
|
||
common.ApiSuccess(c, gin.H{
|
||
"id": app.ID,
|
||
"status": app.Status,
|
||
})
|
||
}
|
||
|
||
// ActivateSupplierApplication godoc
|
||
// @Summary 管理员启用已注销供应商
|
||
// @Description 仅已注销状态可启用;启用后回填用户表 supplier_id 并将申请状态置为审核通过
|
||
// @Tags SupplierAdmin
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body SupplierDeactivateRequest false "启用说明"
|
||
// @Success 200 {object} map[string]interface{} "success + data{id,status}"
|
||
// @Failure 400 {object} map[string]interface{} "参数错误"
|
||
// @Router /user/supplier/application/activate [post]
|
||
func ActivateSupplierApplication(c *gin.Context) {
|
||
var req SupplierDeactivateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的参数"})
|
||
return
|
||
}
|
||
if req.SupplierID <= 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "启用供应商时必须提供有效的supplier_id"})
|
||
return
|
||
}
|
||
reason := strings.TrimSpace(req.Reason)
|
||
app, err := model.ActivateSupplierApplication(c.GetInt("id"), c.GetInt("role"), req.SupplierID, reason)
|
||
if err != nil {
|
||
if errors.Is(err, model.ErrSupplierApplicationStatusNotDeactivated) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "当前供应商状态不支持启用"})
|
||
return
|
||
}
|
||
if model.IsSupplierApplicationNotFound(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "未找到可启用的供应商申请"})
|
||
return
|
||
}
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
_ = service.PublishUserMessage(&model.UserMessage{
|
||
ReceiverUserID: app.ApplicantUserID,
|
||
ReceiverMinRole: 0,
|
||
Type: model.UserMessageTypeSupplierApproved,
|
||
Title: "供应商已启用",
|
||
Content: fmt.Sprintf("你的供应商“%s”已重新启用。", app.CompanyName),
|
||
BizType: model.UserMessageBizTypeSupplierApplication,
|
||
BizID: app.ID,
|
||
})
|
||
common.ApiSuccess(c, gin.H{
|
||
"id": app.ID,
|
||
"status": app.Status,
|
||
})
|
||
}
|
||
|
||
// CreateMySupplierChannel godoc
|
||
// @Summary 当前供应商新增渠道
|
||
// @Description 仅审核通过的供应商可新增,自动写入 owner_user_id 与 supplier_application_id
|
||
// @Tags Supplier
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body AddChannelRequest true "渠道创建参数"
|
||
// @Success 200 {object} map[string]interface{} "创建结果"
|
||
// @Router /user/supplier/channels [post]
|
||
func CreateMySupplierChannel(c *gin.Context) {
|
||
supplierApp, ok := requireApprovedSupplierApplication(c)
|
||
if !ok {
|
||
return
|
||
}
|
||
addChannelRequest := AddChannelRequest{}
|
||
if err := c.ShouldBindJSON(&addChannelRequest); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if err := validateChannel(addChannelRequest.Channel, true); err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
||
return
|
||
}
|
||
addChannelRequest.Channel.CreatedTime = common.GetTimestamp()
|
||
keys := make([]string, 0)
|
||
switch addChannelRequest.Mode {
|
||
case "multi_to_single":
|
||
addChannelRequest.Channel.ChannelInfo.IsMultiKey = true
|
||
addChannelRequest.Channel.ChannelInfo.MultiKeyMode = addChannelRequest.MultiKeyMode
|
||
if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi && addChannelRequest.Channel.GetOtherSettings().VertexKeyType != dto.VertexKeyTypeAPIKey {
|
||
array, err := getVertexArrayKeys(addChannelRequest.Channel.Key)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
||
return
|
||
}
|
||
addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(array)
|
||
addChannelRequest.Channel.Key = strings.Join(array, "\n")
|
||
} else {
|
||
cleanKeys := make([]string, 0)
|
||
for _, key := range strings.Split(addChannelRequest.Channel.Key, "\n") {
|
||
if key == "" {
|
||
continue
|
||
}
|
||
key = strings.TrimSpace(key)
|
||
cleanKeys = append(cleanKeys, key)
|
||
}
|
||
addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(cleanKeys)
|
||
addChannelRequest.Channel.Key = strings.Join(cleanKeys, "\n")
|
||
}
|
||
keys = []string{addChannelRequest.Channel.Key}
|
||
case "batch":
|
||
if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi && addChannelRequest.Channel.GetOtherSettings().VertexKeyType != dto.VertexKeyTypeAPIKey {
|
||
array, err := getVertexArrayKeys(addChannelRequest.Channel.Key)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
||
return
|
||
}
|
||
keys = array
|
||
} else {
|
||
keys = strings.Split(addChannelRequest.Channel.Key, "\n")
|
||
}
|
||
case "single":
|
||
keys = []string{addChannelRequest.Channel.Key}
|
||
default:
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "不支持的添加模式"})
|
||
return
|
||
}
|
||
channels := make([]model.Channel, 0, len(keys))
|
||
for _, key := range keys {
|
||
if key == "" {
|
||
continue
|
||
}
|
||
localChannel := addChannelRequest.Channel
|
||
localChannel.Key = key
|
||
localChannel.OwnerUserID = c.GetInt("id")
|
||
localChannel.SupplierApplicationID = supplierApp.ID
|
||
if addChannelRequest.BatchAddSetKeyPrefix2Name && len(keys) > 1 {
|
||
keyPrefix := localChannel.Key
|
||
if len(localChannel.Key) > 8 {
|
||
keyPrefix = localChannel.Key[:8]
|
||
}
|
||
localChannel.Name = fmt.Sprintf("%s %s", localChannel.Name, keyPrefix)
|
||
}
|
||
channels = append(channels, *localChannel)
|
||
}
|
||
if err := model.BatchInsertChannels(channels); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
service.ResetProxyClientCache()
|
||
common.ApiSuccess(c, gin.H{"created": len(channels)})
|
||
}
|
||
|
||
// ListMySupplierChannels godoc
|
||
// @Summary 查询当前供应商渠道列表
|
||
// @Description 供应商返回本人渠道;管理员返回所有供应商渠道
|
||
// @Tags Supplier
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param p query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Param channel_id query int false "渠道ID"
|
||
// @Param name query string false "渠道名称(模糊)"
|
||
// @Param key query string false "渠道密钥(精确或模糊)"
|
||
// @Param base_url query string false "API地址(模糊)"
|
||
// @Param model query string false "模型关键字(模糊)"
|
||
// @Param group query string false "分组"
|
||
// @Success 200 {object} map[string]interface{} "分页结果"
|
||
// @Router /user/supplier/channels [get]
|
||
func ListMySupplierChannels(c *gin.Context) {
|
||
pageInfo := common.GetPageQuery(c)
|
||
channelID, parseErr := model.ParseSupplierChannelIDFilter(c.Query("channel_id"))
|
||
if parseErr != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "渠道ID参数格式错误"})
|
||
return
|
||
}
|
||
filter := model.SupplierChannelSearchFilter{
|
||
ChannelID: channelID,
|
||
Name: strings.TrimSpace(c.Query("name")),
|
||
Key: strings.TrimSpace(c.Query("key")),
|
||
BaseURL: strings.TrimSpace(c.Query("base_url")),
|
||
ModelKeyword: strings.TrimSpace(c.Query("model")),
|
||
Group: strings.TrimSpace(c.Query("group")),
|
||
}
|
||
var (
|
||
items []*model.Channel
|
||
total int64
|
||
err error
|
||
)
|
||
if c.GetInt("role") >= common.RoleAdminUser {
|
||
items, total, err = model.SearchSupplierChannels(nil, pageInfo.GetStartIdx(), pageInfo.GetPageSize(), filter)
|
||
} else {
|
||
ownerUserID := c.GetInt("id")
|
||
items, total, err = model.SearchSupplierChannels(&ownerUserID, pageInfo.GetStartIdx(), pageInfo.GetPageSize(), filter)
|
||
}
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
for _, item := range items {
|
||
clearChannelInfo(item)
|
||
}
|
||
common.ApiSuccess(c, gin.H{
|
||
"items": items,
|
||
"total": total,
|
||
"page": pageInfo.GetPage(),
|
||
"page_size": pageInfo.GetPageSize(),
|
||
})
|
||
}
|
||
|
||
// CreateMySupplierModel godoc
|
||
// @Summary 当前供应商新增模型
|
||
// @Description 仅审核通过供应商可新增,自动写入 owner_user_id 与 supplier_application_id
|
||
// @Tags Supplier
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security CookieAuth
|
||
// @Security ApiUserID
|
||
// @Param request body model.Model true "模型创建参数"
|
||
// @Success 200 {object} map[string]interface{} "创建结果"
|
||
// @Router /user/supplier/models [post]
|
||
func CreateMySupplierModel(c *gin.Context) {
|
||
supplierApp, ok := requireApprovedSupplierApplication(c)
|
||
if !ok {
|
||
return
|
||
}
|
||
var m model.Model
|
||
if err := c.ShouldBindJSON(&m); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if m.ModelName == "" {
|
||
common.ApiErrorMsg(c, "模型名称不能为空")
|
||
return
|
||
}
|
||
dup, err := model.IsModelNameDuplicated(0, m.ModelName)
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
if dup {
|
||
common.ApiErrorMsg(c, "模型名称已存在")
|
||
return
|
||
}
|
||
m.OwnerUserID = c.GetInt("id")
|
||
m.SupplierApplicationID = supplierApp.ID
|
||
if err := m.Insert(); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
model.RefreshPricing()
|
||
common.ApiSuccess(c, &m)
|
||
}
|
||
|
||
// ListMySupplierModels godoc
|
||
// @Summary 查询当前供应商模型列表
|
||
// @Description 仅返回当前登录供应商创建的模型
|
||
// @Tags Supplier
|
||
// @Produce json
|
||
// @Security ApiKeyAuth
|
||
// @Security ApiUserID
|
||
// @Param p query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Param model_name query string false "模型名称(模糊)"
|
||
// @Param model_type query string false "模型类型(映射 vendor,支持名称或ID)"
|
||
// @Success 200 {object} map[string]interface{} "分页结果"
|
||
// @Router /user/supplier/models [get]
|
||
func ListMySupplierModels(c *gin.Context) {
|
||
pageInfo := common.GetPageQuery(c)
|
||
keyword := strings.TrimSpace(c.Query("model_name"))
|
||
vendor := strings.TrimSpace(c.Query("model_type"))
|
||
var (
|
||
items []*model.Model
|
||
total int64
|
||
err error
|
||
)
|
||
if c.GetInt("role") >= common.RoleAdminUser {
|
||
items, total, err = model.SearchSupplierModels(nil, keyword, vendor, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
|
||
} else {
|
||
ownerUserID := c.GetInt("id")
|
||
items, total, err = model.SearchSupplierModels(&ownerUserID, keyword, vendor, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
|
||
}
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
enrichModels(items)
|
||
common.ApiSuccess(c, gin.H{
|
||
"items": items,
|
||
"total": total,
|
||
"page": pageInfo.GetPage(),
|
||
"page_size": pageInfo.GetPageSize(),
|
||
})
|
||
}
|