This commit is contained in:
2025-07-28 01:46:39 +08:00
parent b03129667a
commit 357639462a
219 changed files with 21634 additions and 8138 deletions

View File

@@ -16,18 +16,21 @@ import (
// 这是企业认证流程的核心聚合根,封装了完整的认证业务逻辑和状态管理
type Certification struct {
// === 基础信息 ===
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
ID string `gorm:"primaryKey;type:varchar(64)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;unique" json:"user_id" comment:"申请用户ID"`
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
// === 流程时间戳 - 记录每个关键步骤的完成时间 ===
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
ContractFileCreatedAt *time.Time `json:"contract_file_created_at,omitempty" comment:"合同文件生成时间"`
// === e签宝相关信息 ===
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"企业认证流程ID"`
AuthURL string `gorm:"type:varchar(500)" json:"auth_url,omitempty" comment:"企业认证链接"`
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
@@ -110,12 +113,6 @@ func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus,
if !c.validateActorPermission(targetStatus, actor) {
return false, fmt.Sprintf("%s 无权执行此状态转换", enums.GetActorTypeName(actor))
}
// 检查业务规则
if err := c.validateBusinessRules(targetStatus, actor); err != nil {
return false, err.Error()
}
return true, ""
}
@@ -157,7 +154,7 @@ func (c *Certification) TransitionTo(targetStatus enums.CertificationStatus, act
// ================ 业务操作方法 ================
// SubmitEnterpriseInfo 提交企业信息
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo) error {
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo, authURL string, authFlowID string) error {
// 验证当前状态
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
@@ -167,7 +164,12 @@ func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.Enter
if err := enterpriseInfo.Validate(); err != nil {
return fmt.Errorf("企业信息验证失败: %w", err)
}
if authURL != "" {
c.AuthURL = authURL
}
if authFlowID != "" {
c.AuthFlowID = authFlowID
}
// 状态转换
if err := c.TransitionTo(enums.StatusInfoSubmitted, enums.ActorTypeUser, c.UserID, "用户提交企业信息"); err != nil {
return err
@@ -184,6 +186,26 @@ func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.Enter
return nil
}
// 完成企业认证
func (c *Certification) CompleteEnterpriseVerification() error {
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许完成企业认证", enums.GetStatusName(c.Status))
}
if err := c.TransitionTo(enums.StatusEnterpriseVerified, enums.ActorTypeSystem, "system", "企业认证成功"); err != nil {
return err
}
c.addDomainEvent(&EnterpriseVerificationSuccessEvent{
CertificationID: c.ID,
UserID: c.UserID,
AuthFlowID: "",
VerifiedAt: time.Now(),
})
return nil
}
// HandleEnterpriseVerificationCallback 处理企业认证回调
func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authFlowID string, failureReason enums.FailureReason, message string) error {
// 验证当前状态
@@ -227,17 +249,19 @@ func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authF
}
// ApplyContract 申请合同签署
func (c *Certification) ApplyContract() error {
func (c *Certification) ApplyContract(EsignFlowID string, ContractSignURL string) error {
// 验证当前状态
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("当前状态 %s 不允许申请合同", enums.GetStatusName(c.Status))
}
// 状态转换
if err := c.TransitionTo(enums.StatusContractApplied, enums.ActorTypeUser, c.UserID, "用户申请合同签署"); err != nil {
return err
}
c.EsignFlowID = EsignFlowID
c.ContractSignURL = ContractSignURL
now := time.Now()
c.ContractFileCreatedAt = &now
// 添加业务事件
c.addDomainEvent(&ContractAppliedEvent{
CertificationID: c.ID,
@@ -248,6 +272,15 @@ func (c *Certification) ApplyContract() error {
return nil
}
// AddContractFileID 生成合同文件
func (c *Certification) AddContractFileID(contractFileID string, contractURL string) error {
c.ContractFileID = contractFileID
c.ContractURL = contractURL
now := time.Now()
c.ContractFileCreatedAt = &now
return nil
}
// UpdateContractInfo 更新合同信息
func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractInfo) error {
// 验证合同信息
@@ -264,57 +297,76 @@ func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractI
return nil
}
// HandleContractSignCallback 处理合同签署回调
func (c *Certification) HandleContractSignCallback(success bool, contractURL string, failureReason enums.FailureReason, message string) error {
// SignSuccess 签署成功
func (c *Certification) SignSuccess() error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
if success {
// 签署成功 - 认证完成
c.ContractURL = contractURL
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功,认证完成"); err != nil {
return err
}
c.addDomainEvent(&ContractSignedEvent{
CertificationID: c.ID,
UserID: c.UserID,
ContractURL: contractURL,
SignedAt: time.Now(),
})
c.addDomainEvent(&CertificationCompletedEvent{
CertificationID: c.ID,
UserID: c.UserID,
CompletedAt: time.Now(),
})
} else {
// 签署失败
c.setFailureInfo(failureReason, message)
var targetStatus enums.CertificationStatus
if failureReason == enums.FailureReasonContractExpired {
targetStatus = enums.StatusContractExpired
} else {
targetStatus = enums.StatusContractRejected
}
if err := c.TransitionTo(targetStatus, enums.ActorTypeEsign, "esign_system", "合同签署失败"); err != nil {
return err
}
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: failureReason,
FailureMessage: message,
FailedAt: time.Now(),
})
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功"); err != nil {
return err
}
c.addDomainEvent(&ContractSignedEvent{
CertificationID: c.ID,
UserID: c.UserID,
SignedAt: time.Now(),
})
return nil
}
// ContractRejection 处理合同拒签
func (c *Certification) ContractRejection(message string) error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同拒签", enums.GetStatusName(c.Status))
}
// 设置失败信息
c.setFailureInfo(enums.FailureReasonContractRejectedByUser, message)
// 状态转换
if err := c.TransitionTo(enums.StatusContractRejected, enums.ActorTypeEsign, "esign_system", "合同签署被拒绝"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: enums.FailureReasonContractRejectedByUser,
FailureMessage: message,
FailedAt: time.Now(),
})
return nil
}
// ContractExpiration 处理合同过期
func (c *Certification) ContractExpiration() error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同过期", enums.GetStatusName(c.Status))
}
// 设置失败信息
c.setFailureInfo(enums.FailureReasonContractExpired, "合同签署已超时")
// 状态转换
if err := c.TransitionTo(enums.StatusContractExpired, enums.ActorTypeSystem, "system", "合同签署超时"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: enums.FailureReasonContractExpired,
FailureMessage: "合同签署已超时",
FailedAt: time.Now(),
})
return nil
}
@@ -369,7 +421,58 @@ func (c *Certification) RetryFromFailure(actor enums.ActorType, actorID string)
return nil
}
// CompleteCertification 完成认证
func (c *Certification) CompleteCertification() error {
// 验证当前状态
if c.Status != enums.StatusContractSigned {
return fmt.Errorf("当前状态 %s 不允许完成认证", enums.GetStatusName(c.Status))
}
// 验证合同信息完整性
if c.ContractFileID == "" || c.EsignFlowID == "" || c.ContractURL == "" {
return errors.New("合同信息不完整,无法完成认证")
}
// 状态转换
if err := c.TransitionTo(enums.StatusCompleted, enums.ActorTypeSystem, "system", "系统处理完成,认证成功"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&CertificationCompletedEvent{
CertificationID: c.ID,
UserID: c.UserID,
CompletedAt: time.Now(),
})
return nil
}
// ================ 查询方法 ================
// GetDataByStatus 根据当前状态获取对应的数据
func (c *Certification) GetDataByStatus() map[string]interface{} {
data := map[string]interface{}{}
switch c.Status {
case enums.StatusInfoSubmitted:
data["auth_url"] = c.AuthURL
case enums.StatusInfoRejected:
data["failure_reason"] = c.FailureReason
data["failure_message"] = c.FailureMessage
case enums.StatusEnterpriseVerified:
data["ContractURL"] = c.ContractURL
case enums.StatusContractApplied:
data["contract_sign_url"] = c.ContractSignURL
case enums.StatusContractSigned:
data["contract_url"] = c.ContractURL
case enums.StatusCompleted:
data["contract_url"] = c.ContractURL
data["completed_at"] = c.CompletedAt
case enums.StatusContractRejected:
data["failure_reason"] = c.FailureReason
data["failure_message"] = c.FailureMessage
}
return data
}
// GetProgress 获取认证进度百分比
func (c *Certification) GetProgress() int {
@@ -414,9 +517,9 @@ func (c *Certification) IsFinalStatus() bool {
return enums.IsFinalStatus(c.Status)
}
// IsCompleted 是否已完成认证
// IsCompleted 是否已完成
func (c *Certification) IsCompleted() bool {
return c.Status == enums.StatusContractSigned
return c.Status == enums.StatusCompleted
}
// GetNextValidStatuses 获取下一个有效状态
@@ -429,6 +532,28 @@ func (c *Certification) GetFailureInfo() (enums.FailureReason, string) {
return c.FailureReason, c.FailureMessage
}
// IsContractFileExpired 判断合同文件是否过期生成后50分钟过期
func (c *Certification) IsContractFileExpired() bool {
if c.ContractFileCreatedAt == nil && c.Status == enums.StatusEnterpriseVerified {
// 60分钟前
t := time.Now().Add(-60 * time.Minute)
c.ContractFileCreatedAt = &t
return true
}
if c.ContractFileCreatedAt != nil {
return time.Since(*c.ContractFileCreatedAt) > 50*time.Minute
}
return false
}
// IsContractFileNeedUpdate 是否需要更新合同文件
func (c *Certification) IsContractFileNeedUpdate() bool {
if c.IsContractFileExpired() && c.Status == enums.StatusEnterpriseVerified {
return true
}
return false
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
@@ -445,17 +570,20 @@ func (c *Certification) ValidateBusinessRules() error {
// 状态相关验证
switch c.Status {
case enums.StatusEnterpriseVerified:
if c.AuthFlowID == "" {
return errors.New("企业认证状态下必须有认证流程ID")
}
case enums.StatusContractApplied:
if c.AuthFlowID == "" {
return errors.New("合同申请状态下必须有企业认证流程ID")
if c.ContractURL == "" {
return errors.New("企业认证成功后合同文件ID和合同URL不能为空")
}
case enums.StatusContractSigned:
if c.ContractFileID == "" || c.EsignFlowID == "" {
return errors.New("合同签署状态下必须有完整的合同信息")
}
case enums.StatusCompleted:
if c.ContractFileID == "" || c.EsignFlowID == "" || c.ContractURL == "" {
return errors.New("认证完成状态下必须有完整的合同信息")
}
if c.CompletedAt == nil {
return errors.New("认证完成状态下必须有完成时间")
}
}
// 失败状态验证
@@ -475,13 +603,14 @@ func (c *Certification) ValidateBusinessRules() error {
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
// 定义状态转换的权限规则
permissions := map[enums.CertificationStatus][]enums.ActorType{
enums.StatusInfoSubmitted: {enums.ActorTypeUser},
enums.StatusInfoSubmitted: {enums.ActorTypeUser, enums.ActorTypeAdmin},
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractApplied: {enums.ActorTypeUser},
enums.StatusContractApplied: {enums.ActorTypeUser, enums.ActorTypeAdmin},
enums.StatusContractSigned: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractExpired: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusCompleted: {enums.ActorTypeSystem, enums.ActorTypeAdmin},
}
allowedActors, exists := permissions[targetStatus]
@@ -498,44 +627,6 @@ func (c *Certification) validateActorPermission(targetStatus enums.Certification
return false
}
// validateBusinessRules 验证业务规则
func (c *Certification) validateBusinessRules(targetStatus enums.CertificationStatus, actor enums.ActorType) error {
// 用户操作验证
if actor == enums.ActorTypeUser {
switch targetStatus {
case enums.StatusInfoSubmitted:
// 用户提交企业信息时的验证
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
}
case enums.StatusContractApplied:
// 用户申请合同时的验证
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("必须先完成企业认证才能申请合同")
}
if c.AuthFlowID == "" {
return errors.New("缺少企业认证流程ID")
}
}
}
// e签宝回调验证
if actor == enums.ActorTypeEsign {
switch targetStatus {
case enums.StatusEnterpriseVerified, enums.StatusInfoRejected:
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
}
case enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired:
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
}
}
return nil
}
// ================ 辅助方法 ================
// updateTimestampByStatus 根据状态更新对应的时间戳
@@ -551,6 +642,8 @@ func (c *Certification) updateTimestampByStatus(status enums.CertificationStatus
c.ContractAppliedAt = &now
case enums.StatusContractSigned:
c.ContractSignedAt = &now
case enums.StatusCompleted:
c.CompletedAt = &now
}
}

View File

@@ -18,6 +18,8 @@ type EnterpriseInfoSubmitRecord struct {
LegalPersonName string `json:"legal_person_name" gorm:"type:varchar(50);not null"`
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
EnterpriseAddress string `json:"enterprise_address" gorm:"type:varchar(200);not null"` // 新增企业地址
EnterpriseEmail string `json:"enterprise_email" gorm:"type:varchar(100);not null"` // 企业邮箱
// 提交状态
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
@@ -38,7 +40,7 @@ func (EnterpriseInfoSubmitRecord) TableName() string {
// NewEnterpriseInfoSubmitRecord 创建新的企业信息提交记录
func NewEnterpriseInfoSubmitRecord(
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string,
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string,
) *EnterpriseInfoSubmitRecord {
return &EnterpriseInfoSubmitRecord{
ID: uuid.New().String(),
@@ -48,6 +50,8 @@ func NewEnterpriseInfoSubmitRecord(
LegalPersonName: legalPersonName,
LegalPersonID: legalPersonID,
LegalPersonPhone: legalPersonPhone,
EnterpriseAddress: enterpriseAddress,
EnterpriseEmail: enterpriseEmail,
Status: "submitted",
SubmitAt: time.Now(),
CreatedAt: time.Now(),

View File

@@ -12,35 +12,35 @@ import (
// 封装电子合同相关的核心信息,包含合同状态和签署流程管理
type ContractInfo struct {
// 合同基本信息
ContractFileID string `json:"contract_file_id"` // 合同文件ID
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
ContractURL string `json:"contract_url"` // 合同文件访问链接
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
ContractFileID string `json:"contract_file_id"` // 合同文件ID
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
ContractURL string `json:"contract_url"` // 合同文件访问链接
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
// 合同元数据
ContractTitle string `json:"contract_title"` // 合同标题
ContractVersion string `json:"contract_version"` // 合同版本
TemplateID string `json:"template_id"` // 模板ID
ContractTitle string `json:"contract_title"` // 合同标题
ContractVersion string `json:"contract_version"` // 合同版本
TemplateID string `json:"template_id"` // 模板ID
// 签署相关信息
SignerAccount string `json:"signer_account"` // 签署人账号
SignerName string `json:"signer_name"` // 签署人姓名
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
TransactorName string `json:"transactor_name"` // 经办人姓名
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
SignerAccount string `json:"signer_account"` // 签署人账号
SignerName string `json:"signer_name"` // 签署人姓名
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
TransactorName string `json:"transactor_name"` // 经办人姓名
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
// 时间信息
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
// 状态信息
Status string `json:"status"` // 合同状态
SignProgress int `json:"sign_progress"` // 签署进度
Status string `json:"status"` // 合同状态
SignProgress int `json:"sign_progress"` // 签署进度
// 附加信息
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
}
// ContractStatus 合同状态常量
@@ -57,19 +57,19 @@ const (
// NewContractInfo 创建合同信息值对象
func NewContractInfo(contractFileID, esignFlowID, contractURL, contractSignURL string) (*ContractInfo, error) {
info := &ContractInfo{
ContractFileID: strings.TrimSpace(contractFileID),
EsignFlowID: strings.TrimSpace(esignFlowID),
ContractURL: strings.TrimSpace(contractURL),
ContractSignURL: strings.TrimSpace(contractSignURL),
Status: ContractStatusGenerated,
SignProgress: 0,
Metadata: make(map[string]interface{}),
ContractFileID: strings.TrimSpace(contractFileID),
EsignFlowID: strings.TrimSpace(esignFlowID),
ContractURL: strings.TrimSpace(contractURL),
ContractSignURL: strings.TrimSpace(contractSignURL),
Status: ContractStatusGenerated,
SignProgress: 0,
Metadata: make(map[string]interface{}),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("合同信息验证失败: %w", err)
}
return info, nil
}
@@ -78,27 +78,27 @@ func (c *ContractInfo) Validate() error {
if err := c.validateContractFileID(); err != nil {
return err
}
if err := c.validateEsignFlowID(); err != nil {
return err
}
if err := c.validateContractURL(); err != nil {
return err
}
if err := c.validateContractSignURL(); err != nil {
return err
}
if err := c.validateSignerInfo(); err != nil {
return err
}
if err := c.validateStatus(); err != nil {
return err
}
return nil
}
@@ -107,12 +107,12 @@ func (c *ContractInfo) validateContractFileID() error {
if c.ContractFileID == "" {
return errors.New("合同文件ID不能为空")
}
// 简单的格式验证
if len(c.ContractFileID) < 10 {
return errors.New("合同文件ID格式不正确")
}
return nil
}
@@ -121,12 +121,12 @@ func (c *ContractInfo) validateEsignFlowID() error {
if c.EsignFlowID == "" {
return errors.New("e签宝流程ID不能为空")
}
// 简单的格式验证
if len(c.EsignFlowID) < 10 {
return errors.New("e签宝流程ID格式不正确")
}
return nil
}
@@ -135,18 +135,18 @@ func (c *ContractInfo) validateContractURL() error {
if c.ContractURL == "" {
return errors.New("合同访问链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractURL)
if err != nil {
return fmt.Errorf("合同访问链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同访问链接格式不正确必须以http://或https://开头")
}
return nil
}
@@ -155,18 +155,18 @@ func (c *ContractInfo) validateContractSignURL() error {
if c.ContractSignURL == "" {
return errors.New("合同签署链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractSignURL)
if err != nil {
return fmt.Errorf("合同签署链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同签署链接格式不正确必须以http://或https://开头")
}
return nil
}
@@ -177,11 +177,11 @@ func (c *ContractInfo) validateSignerInfo() error {
if c.SignerAccount == "" {
return errors.New("签署人账号不能为空")
}
if c.SignerName == "" {
return errors.New("签署人姓名不能为空")
}
if c.TransactorPhone != "" {
// 手机号格式验证
phonePattern := `^1[3-9]\d{9}$`
@@ -189,12 +189,12 @@ func (c *ContractInfo) validateSignerInfo() error {
if err != nil {
return fmt.Errorf("经办人手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人手机号格式不正确")
}
}
if c.TransactorIDCardNum != "" {
// 身份证号格式验证
idPattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
@@ -202,13 +202,13 @@ func (c *ContractInfo) validateSignerInfo() error {
if err != nil {
return fmt.Errorf("经办人身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人身份证号格式不正确")
}
}
}
return nil
}
@@ -223,13 +223,13 @@ func (c *ContractInfo) validateStatus() error {
ContractStatusRejected,
ContractStatusCancelled,
}
for _, status := range validStatuses {
if c.Status == status {
return nil
}
}
return fmt.Errorf("无效的合同状态: %s", c.Status)
}
@@ -240,7 +240,7 @@ func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone,
c.TransactorPhone = strings.TrimSpace(transactorPhone)
c.TransactorName = strings.TrimSpace(transactorName)
c.TransactorIDCardNum = strings.TrimSpace(transactorIDCardNum)
return c.validateSignerInfo()
}
@@ -248,15 +248,15 @@ func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone,
func (c *ContractInfo) UpdateStatus(status string) error {
oldStatus := c.Status
c.Status = status
if err := c.validateStatus(); err != nil {
c.Status = oldStatus // 回滚
return err
}
// 根据状态更新进度
c.updateProgressByStatus()
return nil
}
@@ -271,7 +271,7 @@ func (c *ContractInfo) updateProgressByStatus() {
ContractStatusRejected: 50,
ContractStatusCancelled: 0,
}
if progress, exists := progressMap[c.Status]; exists {
c.SignProgress = progress
}
@@ -283,7 +283,7 @@ func (c *ContractInfo) MarkAsSigning() error {
c.SignProgress = 50
now := time.Now()
c.SignFlowCreatedAt = &now
return nil
}
@@ -293,7 +293,7 @@ func (c *ContractInfo) MarkAsSigned() error {
c.SignProgress = 100
now := time.Now()
c.SignedAt = &now
return nil
}
@@ -302,7 +302,7 @@ func (c *ContractInfo) MarkAsExpired() error {
c.Status = ContractStatusExpired
now := time.Now()
c.ExpiresAt = &now
return nil
}
@@ -310,7 +310,7 @@ func (c *ContractInfo) MarkAsExpired() error {
func (c *ContractInfo) MarkAsRejected() error {
c.Status = ContractStatusRejected
c.SignProgress = 50
return nil
}
@@ -319,7 +319,7 @@ func (c *ContractInfo) IsExpired() bool {
if c.ExpiresAt == nil {
return false
}
return time.Now().After(*c.ExpiresAt)
}
@@ -344,7 +344,7 @@ func (c *ContractInfo) GetStatusName() string {
ContractStatusRejected: "被拒绝",
ContractStatusCancelled: "已取消",
}
if name, exists := statusNames[c.Status]; exists {
return name
}
@@ -364,7 +364,7 @@ func (c *ContractInfo) GetMaskedSignerAccount() string {
if len(c.SignerAccount) <= 6 {
return c.SignerAccount
}
// 保留前3位和后3位中间用*替代
return c.SignerAccount[:3] + "***" + c.SignerAccount[len(c.SignerAccount)-3:]
}
@@ -374,7 +374,7 @@ func (c *ContractInfo) GetMaskedTransactorPhone() string {
if len(c.TransactorPhone) != 11 {
return c.TransactorPhone
}
// 保留前3位和后4位中间用*替代
return c.TransactorPhone[:3] + "****" + c.TransactorPhone[7:]
}
@@ -384,7 +384,7 @@ func (c *ContractInfo) GetMaskedTransactorIDCardNum() string {
if len(c.TransactorIDCardNum) != 18 {
return c.TransactorIDCardNum
}
// 保留前6位和后4位中间用*替代
return c.TransactorIDCardNum[:6] + "********" + c.TransactorIDCardNum[14:]
}
@@ -411,7 +411,7 @@ func (c *ContractInfo) Equals(other *ContractInfo) bool {
if other == nil {
return false
}
return c.ContractFileID == other.ContractFileID &&
c.EsignFlowID == other.EsignFlowID &&
c.Status == other.Status
@@ -435,7 +435,7 @@ func (c *ContractInfo) Clone() *ContractInfo {
Status: c.Status,
SignProgress: c.SignProgress,
}
// 复制时间字段
if c.GeneratedAt != nil {
generatedAt := *c.GeneratedAt
@@ -453,7 +453,7 @@ func (c *ContractInfo) Clone() *ContractInfo {
expiresAt := *c.ExpiresAt
cloned.ExpiresAt = &expiresAt
}
// 复制元数据
if c.Metadata != nil {
cloned.Metadata = make(map[string]interface{})
@@ -461,38 +461,38 @@ func (c *ContractInfo) Clone() *ContractInfo {
cloned.Metadata[k] = v
}
}
return cloned
}
// String 返回合同信息的字符串表示
func (c *ContractInfo) String() string {
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
c.ContractFileID,
c.EsignFlowID,
c.GetStatusName(),
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
c.ContractFileID,
c.EsignFlowID,
c.GetStatusName(),
c.SignProgress)
}
// ToMap 转换为map格式用于序列化
func (c *ContractInfo) ToMap() map[string]interface{} {
result := map[string]interface{}{
"contract_file_id": c.ContractFileID,
"esign_flow_id": c.EsignFlowID,
"contract_url": c.ContractURL,
"contract_sign_url": c.ContractSignURL,
"contract_title": c.ContractTitle,
"contract_version": c.ContractVersion,
"template_id": c.TemplateID,
"signer_account": c.SignerAccount,
"signer_name": c.SignerName,
"transactor_phone": c.TransactorPhone,
"transactor_name": c.TransactorName,
"transactor_id_card_num": c.TransactorIDCardNum,
"status": c.Status,
"sign_progress": c.SignProgress,
"contract_file_id": c.ContractFileID,
"esign_flow_id": c.EsignFlowID,
"contract_url": c.ContractURL,
"contract_sign_url": c.ContractSignURL,
"contract_title": c.ContractTitle,
"contract_version": c.ContractVersion,
"template_id": c.TemplateID,
"signer_account": c.SignerAccount,
"signer_name": c.SignerName,
"transactor_phone": c.TransactorPhone,
"transactor_name": c.TransactorName,
"transactor_id_card_num": c.TransactorIDCardNum,
"status": c.Status,
"sign_progress": c.SignProgress,
}
// 添加时间字段
if c.GeneratedAt != nil {
result["generated_at"] = c.GeneratedAt
@@ -506,11 +506,11 @@ func (c *ContractInfo) ToMap() map[string]interface{} {
if c.ExpiresAt != nil {
result["expires_at"] = c.ExpiresAt
}
// 添加元数据
if c.Metadata != nil {
result["metadata"] = c.Metadata
}
return result
}
}

View File

@@ -11,35 +11,36 @@ import (
// 封装企业认证所需的核心信息,包含完整的业务规则验证
type EnterpriseInfo struct {
// 企业基本信息
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
// 法定代表人信息
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
LegalPersonPhone string `json:"legal_person_phone"` // 法定代表人手机号
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
LegalPersonPhone string `json:"legal_person_phone"` // 法定代表人手机号
// 企业详细信息
RegisteredAddress string `json:"registered_address"` // 注册地址
BusinessScope string `json:"business_scope"` // 经营范围
RegisteredCapital string `json:"registered_capital"` // 注册资本
EstablishmentDate string `json:"establishment_date"` // 成立日期
RegisteredAddress string `json:"registered_address"` // 注册地址
EnterpriseAddress string `json:"enterprise_address"` // 企业地址(新增)
EnterpriseEmail string `json:"enterprise_email"` // 企业邮箱
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string) (*EnterpriseInfo, error) {
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string) (*EnterpriseInfo, error) {
info := &EnterpriseInfo{
CompanyName: strings.TrimSpace(companyName),
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
LegalPersonName: strings.TrimSpace(legalPersonName),
LegalPersonID: strings.TrimSpace(legalPersonID),
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
EnterpriseAddress: strings.TrimSpace(enterpriseAddress),
EnterpriseEmail: strings.TrimSpace(enterpriseEmail),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("企业信息验证失败: %w", err)
}
return info, nil
}
@@ -48,23 +49,31 @@ func (e *EnterpriseInfo) Validate() error {
if err := e.validateCompanyName(); err != nil {
return err
}
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
if err := e.validateLegalPersonName(); err != nil {
return err
}
if err := e.validateLegalPersonID(); err != nil {
return err
}
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
if err := e.validateEnterpriseAddress(); err != nil {
return err
}
if err := e.validateEnterpriseEmail(); err != nil {
return err
}
return nil
}
@@ -73,15 +82,15 @@ func (e *EnterpriseInfo) validateCompanyName() error {
if e.CompanyName == "" {
return errors.New("企业名称不能为空")
}
if len(e.CompanyName) < 2 {
return errors.New("企业名称长度不能少于2个字符")
}
if len(e.CompanyName) > 100 {
return errors.New("企业名称长度不能超过100个字符")
}
// 检查是否包含非法字符
invalidChars := []string{"`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/"}
for _, char := range invalidChars {
@@ -89,7 +98,7 @@ func (e *EnterpriseInfo) validateCompanyName() error {
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
}
}
return nil
}
@@ -98,18 +107,18 @@ func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return errors.New("统一社会信用代码不能为空")
}
// 统一社会信用代码格式验证18位数字和字母
pattern := `^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$`
matched, err := regexp.MatchString(pattern, e.UnifiedSocialCode)
if err != nil {
return fmt.Errorf("统一社会信用代码格式验证错误: %w", err)
}
if !matched {
return errors.New("统一社会信用代码格式不正确应为18位数字和字母组合")
}
return nil
}
@@ -118,26 +127,26 @@ func (e *EnterpriseInfo) validateLegalPersonName() error {
if e.LegalPersonName == "" {
return errors.New("法定代表人姓名不能为空")
}
if len(e.LegalPersonName) < 2 {
return errors.New("法定代表人姓名长度不能少于2个字符")
}
if len(e.LegalPersonName) > 50 {
return errors.New("法定代表人姓名长度不能超过50个字符")
}
// 中文姓名格式验证
pattern := `^[\u4e00-\u9fa5·]+$`
pattern := "^[一-龥·]+$"
matched, err := regexp.MatchString(pattern, e.LegalPersonName)
if err != nil {
return fmt.Errorf("法定代表人姓名格式验证错误: %w", err)
}
if !matched {
return errors.New("法定代表人姓名只能包含中文字符和间隔号")
}
return nil
}
@@ -146,27 +155,27 @@ func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return errors.New("法定代表人身份证号不能为空")
}
// 身份证号格式验证18位
if len(e.LegalPersonID) != 18 {
return errors.New("身份证号必须为18位")
}
pattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
matched, err := regexp.MatchString(pattern, e.LegalPersonID)
if err != nil {
return fmt.Errorf("身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("身份证号格式不正确")
}
// 身份证号校验码验证
if !e.validateIDChecksum() {
return errors.New("身份证号校验码错误")
}
return nil
}
@@ -175,22 +184,22 @@ func (e *EnterpriseInfo) validateIDChecksum() bool {
if len(e.LegalPersonID) != 18 {
return false
}
// 加权因子
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
// 校验码对应表
checkCodes := []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
sum := 0
for i := 0; i < 17; i++ {
digit := int(e.LegalPersonID[i] - '0')
sum += digit * weights[i]
}
checkCodeIndex := sum % 11
expectedCheckCode := checkCodes[checkCodeIndex]
actualCheckCode := strings.ToUpper(string(e.LegalPersonID[17]))
return expectedCheckCode == actualCheckCode
}
@@ -199,18 +208,53 @@ func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return errors.New("法定代表人手机号不能为空")
}
// 手机号格式验证11位数字1开头
pattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(pattern, e.LegalPersonPhone)
if err != nil {
return fmt.Errorf("手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("手机号格式不正确应为11位数字且以1开头")
}
return nil
}
// validateEnterpriseAddress 验证企业地址
func (e *EnterpriseInfo) validateEnterpriseAddress() error {
if strings.TrimSpace(e.EnterpriseAddress) == "" {
return errors.New("企业地址不能为空")
}
if len(e.EnterpriseAddress) < 5 {
return errors.New("企业地址长度不能少于5个字符")
}
if len(e.EnterpriseAddress) > 200 {
return errors.New("企业地址长度不能超过200个字符")
}
return nil
}
// validateEnterpriseEmail 验证企业邮箱
func (e *EnterpriseInfo) validateEnterpriseEmail() error {
if strings.TrimSpace(e.EnterpriseEmail) == "" {
return errors.New("企业邮箱不能为空")
}
if len(e.EnterpriseEmail) < 5 {
return errors.New("企业邮箱长度不能少于5个字符")
}
if len(e.EnterpriseEmail) > 100 {
return errors.New("企业邮箱长度不能超过100个字符")
}
// 邮箱格式验证
emailPattern := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailPattern.MatchString(e.EnterpriseEmail) {
return errors.New("企业邮箱格式不正确")
}
return nil
}
@@ -220,16 +264,16 @@ func (e *EnterpriseInfo) IsComplete() bool {
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != "" &&
e.LegalPersonPhone != ""
e.LegalPersonPhone != "" &&
e.EnterpriseAddress != "" &&
e.EnterpriseEmail != ""
}
// IsDetailComplete 检查企业详细信息是否完整
func (e *EnterpriseInfo) IsDetailComplete() bool {
return e.IsComplete() &&
e.RegisteredAddress != "" &&
e.BusinessScope != "" &&
e.RegisteredCapital != "" &&
e.EstablishmentDate != ""
e.EnterpriseAddress != ""
}
// GetDisplayName 获取显示用的企业名称
@@ -245,7 +289,7 @@ func (e *EnterpriseInfo) GetMaskedUnifiedSocialCode() string {
if len(e.UnifiedSocialCode) != 18 {
return e.UnifiedSocialCode
}
// 保留前6位和后4位中间用*替代
return e.UnifiedSocialCode[:6] + "********" + e.UnifiedSocialCode[14:]
}
@@ -255,7 +299,7 @@ func (e *EnterpriseInfo) GetMaskedLegalPersonID() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
// 保留前6位和后4位中间用*替代
return e.LegalPersonID[:6] + "********" + e.LegalPersonID[14:]
}
@@ -265,7 +309,7 @@ func (e *EnterpriseInfo) GetMaskedLegalPersonPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
// 保留前3位和后4位中间用*替代
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
@@ -275,34 +319,34 @@ func (e *EnterpriseInfo) Equals(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.CompanyName == other.CompanyName &&
e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonName == other.LegalPersonName &&
e.LegalPersonID == other.LegalPersonID &&
e.LegalPersonPhone == other.LegalPersonPhone
e.LegalPersonPhone == other.LegalPersonPhone &&
e.EnterpriseEmail == other.EnterpriseEmail
}
// Clone 创建企业信息的副本
func (e *EnterpriseInfo) Clone() *EnterpriseInfo {
return &EnterpriseInfo{
CompanyName: e.CompanyName,
UnifiedSocialCode: e.UnifiedSocialCode,
LegalPersonName: e.LegalPersonName,
LegalPersonID: e.LegalPersonID,
LegalPersonPhone: e.LegalPersonPhone,
RegisteredAddress: e.RegisteredAddress,
BusinessScope: e.BusinessScope,
RegisteredCapital: e.RegisteredCapital,
EstablishmentDate: e.EstablishmentDate,
CompanyName: e.CompanyName,
UnifiedSocialCode: e.UnifiedSocialCode,
LegalPersonName: e.LegalPersonName,
LegalPersonID: e.LegalPersonID,
LegalPersonPhone: e.LegalPersonPhone,
RegisteredAddress: e.RegisteredAddress,
EnterpriseAddress: e.EnterpriseAddress,
EnterpriseEmail: e.EnterpriseEmail,
}
}
// String 返回企业信息的字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
e.CompanyName,
e.GetMaskedUnifiedSocialCode(),
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
e.CompanyName,
e.GetMaskedUnifiedSocialCode(),
e.LegalPersonName)
}
@@ -315,9 +359,8 @@ func (e *EnterpriseInfo) ToMap() map[string]interface{} {
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
"registered_address": e.RegisteredAddress,
"business_scope": e.BusinessScope,
"registered_capital": e.RegisteredCapital,
"establishment_date": e.EstablishmentDate,
"enterprise_address": e.EnterpriseAddress,
"enterprise_email": e.EnterpriseEmail,
}
}
@@ -331,22 +374,21 @@ func FromMap(data map[string]interface{}) (*EnterpriseInfo, error) {
}
return ""
}
info := &EnterpriseInfo{
CompanyName: getString("company_name"),
UnifiedSocialCode: getString("unified_social_code"),
LegalPersonName: getString("legal_person_name"),
LegalPersonID: getString("legal_person_id"),
LegalPersonPhone: getString("legal_person_phone"),
RegisteredAddress: getString("registered_address"),
BusinessScope: getString("business_scope"),
RegisteredCapital: getString("registered_capital"),
EstablishmentDate: getString("establishment_date"),
CompanyName: getString("company_name"),
UnifiedSocialCode: getString("unified_social_code"),
LegalPersonName: getString("legal_person_name"),
LegalPersonID: getString("legal_person_id"),
LegalPersonPhone: getString("legal_person_phone"),
RegisteredAddress: getString("registered_address"),
EnterpriseAddress: getString("enterprise_address"),
EnterpriseEmail: getString("enterprise_email"),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("从Map创建企业信息失败: %w", err)
}
return info, nil
}
}