by Robin at 20240617
This commit is contained in:
parent
70422fce3a
commit
6159759111
|
@ -104,6 +104,8 @@ const LocalConfigPath = "C:/Users/PC/Desktop/service/etc/mix/mix-local.yaml"
|
|||
|
||||
const ReservedUserIdRegexesConfig = PackageRootPath + "/etc/mix/resource/reg_reserved_user_id_config.xml"
|
||||
|
||||
const EsbRoutingTableConfig = PackageRootPath + "/etc/mix/resource/esb_routing_table_config.xml"
|
||||
|
||||
// H5调用路径
|
||||
const H5CallUrl = "/api/streamer/list_ext_by_user_id"
|
||||
|
||||
|
|
|
@ -214,6 +214,8 @@ var ErrCodeMsgMap = map[ErrCode]string{
|
|||
|
||||
ErrCodeWorkerIdSrvFail: "用户职业者id映射表服务错误",
|
||||
ErrCodeWorkerIdNotExist: "用户职业者id映射表不存在",
|
||||
|
||||
ErrCodeHvyogoSrvFail: "慧用工接口服务错误",
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -519,6 +521,10 @@ const (
|
|||
ErrCodeMediaNotExist ErrCode = -60002 // 媒体不存在
|
||||
ErrCodeMediaUploadFail ErrCode = -60003 // 媒体上传失败
|
||||
|
||||
// Hvyogo: 61xxx
|
||||
ErrCodeHvyogoSrvOk ErrCode = ErrCodeOk
|
||||
ErrCodeHvyogoSrvFail ErrCode = -61001 // 慧用工接口服务错误
|
||||
|
||||
// Websocket: 1xxxxx
|
||||
ErrCodeHandleWsFail ErrCode = -100001 // 长链连接失败
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package interfaces
|
||||
|
||||
type HvyogoSignable interface {
|
||||
GetArgString() string
|
||||
SetSign(string)
|
||||
GetCooperatorId() string
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
package request
|
||||
|
||||
import "fmt"
|
||||
|
||||
// 自由职业者网签查询报文
|
||||
type HYG10000001Req struct {
|
||||
CooperatorId string `json:"cooperatorId"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
WorkerId string `json:"workerId"`
|
||||
Sign string `json:"sign"`
|
||||
*HYGBaseReq
|
||||
Timestamp string `json:"timestamp"`
|
||||
WorkerId string `json:"workerId"`
|
||||
}
|
||||
|
||||
func (req *HYG10000001Req) GetArgString() string {
|
||||
return fmt.Sprintf("cooperatorId=%s×tamp=%s&workerId=%s", req.CooperatorId, req.Timestamp, req.WorkerId)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package request
|
||||
|
||||
import "fmt"
|
||||
|
||||
// 自由职业者信息详情查询
|
||||
type HYG10000002Req struct {
|
||||
CooperatorId string `json:"cooperatorId"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
WorkerId string `json:"workerId"`
|
||||
Sign string `json:"sign"`
|
||||
*HYGBaseReq
|
||||
Timestamp string `json:"timestamp"`
|
||||
WorkerId string `json:"workerId"`
|
||||
}
|
||||
|
||||
func (req *HYG10000002Req) GetArgString() string {
|
||||
return fmt.Sprintf("cooperatorId=%s×tamp=%s&workerId=%s", req.CooperatorId, req.Timestamp, req.WorkerId)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package request
|
|||
|
||||
// 下发(打款)报文
|
||||
type HYG10010001Req struct {
|
||||
CooperatorId string `json:"cooperatorId"` // 商户id
|
||||
*HYGBaseReq
|
||||
Timestamp string `json:"timestamp"` // 时间戳
|
||||
WorkerName string `json:"workerName"` // 收款人姓名
|
||||
ReceiptChannel int `json:"receiptChannel"` // 收款渠道
|
||||
|
@ -19,5 +19,8 @@ type HYG10010001Req struct {
|
|||
WorkTime string `json:"workTime"` // 服务时间
|
||||
PositionId string `json:"positionId"` // 任务ID
|
||||
WeChatAppId string `json:"weChatAppId"` // 微信下发指定AppID
|
||||
Sign string `json:"sign"` // 签名
|
||||
}
|
||||
|
||||
func (req *HYG10010001Req) GetArgString() string {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package request
|
||||
|
||||
type HYGBaseReq struct {
|
||||
CooperatorId string `json:"cooperatorId"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
func (req *HYGBaseReq) SetSign(sign string) {
|
||||
req.Sign = sign
|
||||
}
|
||||
|
||||
func (req *HYGBaseReq) GetCooperatorId() string {
|
||||
return req.CooperatorId
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package request
|
||||
|
||||
type HYGCommonReq struct {
|
||||
CooperatorId string `json:"cooperatorId"`
|
||||
BusinessBody string `json:"businessBody"`
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package response
|
||||
|
||||
// 自由职业者网签查询报文
|
||||
type HYG10000001Resp struct {
|
||||
WorkerId string `json:"workerId"`
|
||||
AgreeState string `json:"agreeState"`
|
||||
AgreeDesc string `json:"agreeDesc"`
|
||||
IdCardFront string `json:"idCardFront"`
|
||||
IdCardBack string `json:"idCardBack"`
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package response
|
||||
|
||||
// 自由职业者详细信息查询报文
|
||||
type HYG10000002Resp struct {
|
||||
WorkerId string `json:"workerId"` // 自由职业者ID
|
||||
WorkerName string `json:"workerName"` // 自由职业者名称
|
||||
WorkerMobile string `json:"workerMobile"` // 自由职业者手机号
|
||||
IdentNo string `json:"identNo"` // 自由职业者证件号
|
||||
CertificateType int `json:"certificateType"` // 自由职业者证件类型
|
||||
BankCardNo string `json:"bankCardNo"` // 收款账号
|
||||
SignState int `json:"signState"` // 签约状态
|
||||
SignTime string `json:"signTime"` // 签约时间
|
||||
SignDesc string `json:"signDesc"` // 签约状态描述
|
||||
VerifiedStatus int `json:"verifiedStatus"` // 实名认证状态
|
||||
VerifiedTime string `json:"verifiedTime"` // 实名认证时间
|
||||
VerifiedDesc string `json:"verifiedDesc"` // 实名认证描述
|
||||
WorkerCfcaSignFlag bool `json:"workerCfcaSignFlag"` // 职业者签约方式
|
||||
CreateTime string `json:"createTime"` // 创建时间
|
||||
UpdateTime string `json:"updateTime"` // 最后更新时间
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package response
|
||||
|
||||
// 下发(打款)报文
|
||||
type HYG10010001Resp struct {
|
||||
DistributeAmount string `json:"distributeAmount"` // 下发金额(与上送金额相同)
|
||||
DistributeId string `json:"distributeId"` // 慧用工平台处理单号
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package proto
|
||||
|
||||
import "service/api/base"
|
||||
|
||||
type ApiQueryAgreeStateReq struct {
|
||||
base.BaseRequest
|
||||
}
|
||||
|
||||
type ApiQueryAgreeStateData struct {
|
||||
StatusCode string `json:"status_code" deepcopier:"field:StatusCode"` // 返回码
|
||||
StatusText string `json:"status_text" deepcopier:"field:StatusText"` // 返回信息
|
||||
AgreeState string `json:"agree_state" deepcopier:"field:AgreeState"` // 签约状态
|
||||
AgreeDesc string `json:"agree_desc" deepcopier:"field:AgreeDesc"` // 签约状态描述
|
||||
}
|
||||
|
||||
type ApiQueryAgreeStateResp struct {
|
||||
base.BaseResponse
|
||||
Data *ApiQueryAgreeStateData `json:"data"`
|
||||
}
|
||||
|
||||
type ApiSingleDistributeReq struct {
|
||||
base.BaseRequest
|
||||
DistributeAmount string `json:"distribute_amount"`
|
||||
}
|
||||
|
||||
type ApiSingleDistributeData struct {
|
||||
StatusCode string `json:"status_code"` // 返回码
|
||||
StatusText string `json:"status_text"` // 返回信息
|
||||
DistributeAmount string `json:"distribute_amount"` // 下发金额
|
||||
DistributeId string `json:"distribute_id"` // 慧用工平台处理单号
|
||||
}
|
||||
|
||||
type ApiSingleDistributeResp struct {
|
||||
base.BaseResponse
|
||||
Data *ApiSingleDistributeData `json:"data"`
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package proto
|
||||
|
||||
type HvyogoCallbackReq struct {
|
||||
type ExtAgreeCallbackReq struct {
|
||||
CooperatorId string `json:"cooperatorId"` // 商户对接唯一标识,手机号
|
||||
BusinessBody string `json:"businessBody" jcrypto:"hyg_aes"` // AES加密后的字符串
|
||||
}
|
||||
|
|
|
@ -1,8 +1,45 @@
|
|||
package proto
|
||||
|
||||
type HvyogoVO struct {
|
||||
type ExtAgreeCallbackVO struct {
|
||||
WorkerId string `json:"workerId"` // 职业者id
|
||||
AgreeState string `json:"agreeState"` // 签约状态
|
||||
AgreeDesc string `json:"agreeDesc"` // 签约状态描述
|
||||
WorkerMobile string `json:"workerMobile"` // 自由职业者手机号
|
||||
}
|
||||
|
||||
type WorkerAgreeStateVO struct {
|
||||
StatusCode string `json:"statusCode"`
|
||||
StatusText string `json:"statusText"`
|
||||
WorkerId string `json:"workerId"`
|
||||
AgreeState string `json:"agreeState"`
|
||||
AgreeDesc string `json:"agreeDesc"`
|
||||
IdCardFront string `json:"idCardFront"`
|
||||
IdCardBack string `json:"idCardBack"`
|
||||
}
|
||||
|
||||
type WorkerFindDetailsVO struct {
|
||||
StatusCode string `json:"statusCode"`
|
||||
StatusText string `json:"statusText"`
|
||||
WorkerId string `json:"workerId"` // 自由职业者ID
|
||||
WorkerName string `json:"workerName"` // 自由职业者名称
|
||||
WorkerMobile string `json:"workerMobile"` // 自由职业者手机号
|
||||
IdentNo string `json:"identNo"` // 自由职业者证件号
|
||||
CertificateType int `json:"certificateType"` // 自由职业者证件类型
|
||||
BankCardNo string `json:"bankCardNo"` // 收款账号
|
||||
SignState int `json:"signState"` // 签约状态
|
||||
SignTime string `json:"signTime"` // 签约时间
|
||||
SignDesc string `json:"signDesc"` // 签约状态描述
|
||||
VerifiedStatus int `json:"verifiedStatus"` // 实名认证状态
|
||||
VerifiedTime string `json:"verifiedTime"` // 实名认证时间
|
||||
VerifiedDesc string `json:"verifiedDesc"` // 实名认证描述
|
||||
WorkerCfcaSignFlag bool `json:"workerCfcaSignFlag"` // 职业者签约方式
|
||||
CreateTime string `json:"createTime"` // 创建时间
|
||||
UpdateTime string `json:"updateTime"` // 最后更新时间
|
||||
}
|
||||
|
||||
type SingleDistributeVO struct {
|
||||
StatusCode string `json:"statusCode"`
|
||||
StatusText string `json:"statusText"`
|
||||
DistributeAmount string `json:"distributeAmount"` // 下发金额(与上送金额相同)
|
||||
DistributeId string `json:"distributeId"` // 慧用工平台处理单号
|
||||
}
|
||||
|
|
|
@ -50,13 +50,10 @@ type OpUpdateResp struct {
|
|||
// op 列表
|
||||
type OpListByMidReq struct {
|
||||
base.BaseRequest
|
||||
Mid int64 `json:"mid"`
|
||||
}
|
||||
|
||||
type OpListByMidData struct {
|
||||
WorkerId *dbstruct.WorkerId `json:"worker_id"`
|
||||
Offset int `json:"offset"`
|
||||
More int `json:"more"`
|
||||
}
|
||||
|
||||
type OpListByMidResp struct {
|
||||
|
|
|
@ -86,11 +86,18 @@ func main() {
|
|||
service.DefaultImageAuditTaskResultHandler = service.NewImageAuditTaskResultHandler()
|
||||
service.DefaultTextAuditTaskResultHandler = service.NewTextAuditTaskResultHandler()
|
||||
service.DefaultVideoModerationTaskResultHandler = service.NewVideoModerationTaskResultHandler()
|
||||
service.DefaultEsbService = service.NewEsbService()
|
||||
service.DefaultHvyogoService = service.NewHvyogoService()
|
||||
err = service.DefaultService.Init(cfg)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Service init fail, err: %v", err)
|
||||
PrintAndExit(msg)
|
||||
}
|
||||
err = service.DefaultEsbService.Init()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("EsbService init fail, err: %v", err)
|
||||
PrintAndExit(msg)
|
||||
}
|
||||
// 初始化媒体填充服务
|
||||
mediafiller.Init(cfg.ServerInfo)
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"service/api/errcode"
|
||||
"service/api/message/response"
|
||||
hvyogoproto "service/api/proto/hvyogo/proto"
|
||||
"service/app/mix/service"
|
||||
"service/bizcommon/util"
|
||||
"service/library/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ApiHvyogoQueryAgreeState(ctx *gin.Context) {
|
||||
|
||||
req := ctx.MustGet("client_req").(*hvyogoproto.ApiQueryAgreeStateReq)
|
||||
|
||||
// 存入数据
|
||||
data, ec := service.DefaultService.ApiHvyogoQueryAgreeState(ctx, req)
|
||||
if ec != errcode.ErrCodeWorkerIdSrvOk {
|
||||
logger.Error("ApiHygQueryAgreeState fail, req: %v, ec: %v", util.ToJson(req), ec)
|
||||
response.ReplyErrCodeMsg(ctx, ec)
|
||||
return
|
||||
}
|
||||
|
||||
ReplyOk(ctx, data)
|
||||
}
|
||||
|
||||
func ApiHvyogoSingleDistribute(ctx *gin.Context) {
|
||||
|
||||
req := ctx.MustGet("client_req").(*hvyogoproto.ApiSingleDistributeReq)
|
||||
|
||||
// 存入数据
|
||||
data, ec := service.DefaultService.ApiHvyogoSingleDistribute(ctx, req)
|
||||
if ec != errcode.ErrCodeWorkerIdSrvOk {
|
||||
logger.Error("ApiHygSingleDistribute fail, req: %v, ec: %v", util.ToJson(req), ec)
|
||||
response.ReplyErrCodeMsg(ctx, ec)
|
||||
return
|
||||
}
|
||||
|
||||
ReplyOk(ctx, data)
|
||||
}
|
|
@ -11,14 +11,14 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func HvyogoCallback(ctx *gin.Context) {
|
||||
func ExtAgreeCallback(ctx *gin.Context) {
|
||||
|
||||
req := ctx.MustGet("client_req").(*hvyogoproto.HvyogoCallbackReq)
|
||||
req := ctx.MustGet("client_req").(*hvyogoproto.ExtAgreeCallbackReq)
|
||||
|
||||
// 存入数据
|
||||
ec := service.DefaultService.SaveHvyogoCallback(ctx, req)
|
||||
ec := service.DefaultService.SaveAgreeCallback(ctx, req)
|
||||
if ec != errcode.ErrCodeWorkerIdSrvOk {
|
||||
logger.Error("SaveHvyogoCallback fail, req: %v, ec: %v", util.ToJson(req), ec)
|
||||
logger.Error("SaveAgreeCallback fail, req: %v, ec: %v", util.ToJson(req), ec)
|
||||
response.ReplyErrCodeMsg(ctx, ec)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -261,6 +261,11 @@ func Init(r *gin.Engine) {
|
|||
apiZoneCollaboratorGroup.POST("delete", middleware.JSONParamValidator(zone_collaborator_proto.ApiDeleteReq{}), middleware.JwtAuthenticator(), ApiDeleteZoneCollaborator)
|
||||
apiZoneCollaboratorGroup.POST("list", middleware.JSONParamValidator(zone_collaborator_proto.ApiListReq{}), middleware.JwtAuthenticator(), ApiGetZoneCollaboratorList)
|
||||
|
||||
// 慧用工
|
||||
apiHvyogoGroup := r.Group("/api/hvyogo")
|
||||
apiHvyogoGroup.POST("query_agree_state", middleware.JSONParamValidator(hvyogoproto.ApiQueryAgreeStateReq{}), middleware.JwtAuthenticator(), ApiHvyogoQueryAgreeState)
|
||||
apiHvyogoGroup.POST("single_distribute", middleware.JSONParamValidator(hvyogoproto.ApiSingleDistributeReq{}), middleware.JwtAuthenticator(), ApiHvyogoSingleDistribute)
|
||||
|
||||
// =============================== 以下是服务,只允许内网调用 ===============================
|
||||
|
||||
// op相关,直接调用服务,不调用gateway
|
||||
|
@ -307,8 +312,9 @@ func Init(r *gin.Engine) {
|
|||
opVasPayGroup.POST("zone_refund_list", middleware.JSONParamValidator(zoneproto.OpZoneRefundListParam{}), OpZoneRefundList)
|
||||
opVasPayGroup.POST("manual_unlock_wechat", middleware.JSONParamValidator(zoneproto.OpManualUnlockWechatParam{}), OpManualUnlockWechat)
|
||||
|
||||
extEsbGroup := r.Group("/ext/hvyogo")
|
||||
extEsbGroup.POST("agree_callback", middleware.JSONParamValidator(hvyogoproto.HvyogoCallbackReq{}), middleware.RequestDecryptor(), HvyogoCallback)
|
||||
// 慧用工
|
||||
extHvyogoGroup := r.Group("/ext/hvyogo")
|
||||
extHvyogoGroup.POST("agree_callback", middleware.JSONParamValidator(hvyogoproto.ExtAgreeCallbackReq{}), middleware.RequestDecryptor(), ExtAgreeCallback)
|
||||
|
||||
// 验证码
|
||||
opVeriCodeGroup := r.Group("/op/veri_code", PrepareOp())
|
||||
|
|
|
@ -5260,7 +5260,7 @@ func (m *Mongo) GetWorkerIdByMid(ctx *gin.Context, req *workeridproto.OpListByMi
|
|||
workerId := &dbstruct.WorkerId{}
|
||||
col := m.getColWorkerId()
|
||||
query := qmgo.M{
|
||||
"mid": req.Mid,
|
||||
"mid": req.BaseRequest.Mid,
|
||||
"del_flag": 0,
|
||||
}
|
||||
err := col.Find(ctx, query).One(workerId)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"service/api/base"
|
||||
"service/api/consts"
|
||||
"service/api/errcode"
|
||||
"service/api/message/request"
|
||||
accountproto "service/api/proto/account/proto"
|
||||
account_cancellationproto "service/api/proto/account_cancellation/proto"
|
||||
accountrelationproto "service/api/proto/accountrelation/proto"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
contact_customer_service_proto "service/api/proto/contact_customer_service/proto"
|
||||
contact_customer_service_sessionproto "service/api/proto/contact_customer_service_session/proto"
|
||||
feedbackproto "service/api/proto/feedback/proto"
|
||||
hvyogoproto "service/api/proto/hvyogo/proto"
|
||||
loginproto "service/api/proto/login/proto"
|
||||
momentproto "service/api/proto/moment/proto"
|
||||
moment_audit_taskproto "service/api/proto/moment_audit_task/proto"
|
||||
|
@ -25,6 +27,7 @@ import (
|
|||
userwxaddcheckproto "service/api/proto/userwxaddcheck/proto"
|
||||
vasproto "service/api/proto/vas/proto"
|
||||
vericodeproto "service/api/proto/vericode/proto"
|
||||
workeridproto "service/api/proto/worker_id/proto"
|
||||
zoneproto "service/api/proto/zone/proto"
|
||||
zone_collaborator_proto "service/api/proto/zone_collaborator/proto"
|
||||
zone_third_partner_proto "service/api/proto/zone_third_partner/proto"
|
||||
|
@ -37,6 +40,7 @@ import (
|
|||
"service/dbstruct"
|
||||
"service/library/apollo"
|
||||
"service/library/logger"
|
||||
"service/library/mycrypto"
|
||||
interceptor "service/library/taginterceptor"
|
||||
"time"
|
||||
|
||||
|
@ -45,6 +49,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/qiniu/qmgo"
|
||||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
// 发送验证码
|
||||
|
@ -3252,3 +3257,69 @@ func (s *Service) ApiGetZoneCollaboratorList(ctx *gin.Context, req *zone_collabo
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) ApiHvyogoQueryAgreeState(ctx *gin.Context, req *hvyogoproto.ApiQueryAgreeStateReq) (data *hvyogoproto.ApiQueryAgreeStateData, ec errcode.ErrCode) {
|
||||
|
||||
// 1.查询手机号
|
||||
account, err := _DefaultAccount.OpListByMid(ctx, &accountproto.OpListByMidReq{
|
||||
Mid: goproto.Int64(req.BaseRequest.Mid),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("OpListByMid fail, err: %v", err)
|
||||
ec = errcode.ErrCodeAccountSrvFail
|
||||
return
|
||||
}
|
||||
if account == nil {
|
||||
logger.Error("No account entity was found")
|
||||
ec = errcode.ErrCodeAccountNotExist
|
||||
return
|
||||
}
|
||||
|
||||
bytes, err := mycrypto.CryptoServiceInstance().AES.Decrypt([]byte(util.DerefString(account.MobilePhone)))
|
||||
if err != nil {
|
||||
logger.Error("AES Decrypt fail, err: %v", err)
|
||||
ec = errcode.ErrCodeDecryptionInterceptFail
|
||||
return
|
||||
}
|
||||
|
||||
// 2.查询workerId
|
||||
workerId, err := _DefaultWorkerId.OpListByMid(ctx, &workeridproto.OpListByMidReq{
|
||||
BaseRequest: req.BaseRequest,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("_DefaultWorkerId OpListByMid fail, err: %v", err)
|
||||
ec = errcode.ErrCodeWorkerIdSrvFail
|
||||
return
|
||||
}
|
||||
if workerId == nil {
|
||||
logger.Error("No worker_id entity was found")
|
||||
ec = errcode.ErrCodeWorkerIdNotExist
|
||||
return
|
||||
}
|
||||
|
||||
// 3.组装查询报文
|
||||
msg := &request.HYG10000001Req{
|
||||
HYGBaseReq: &request.HYGBaseReq{
|
||||
CooperatorId: string(bytes),
|
||||
},
|
||||
WorkerId: workerId.GetWorkerId(),
|
||||
}
|
||||
|
||||
// 4.调用查询接口
|
||||
resp, err := DefaultHvyogoService.WorkerAgreeState(msg)
|
||||
if err != nil {
|
||||
logger.Error("DefaultHvyogoService WorkerAgreeState fail, err: %v", err)
|
||||
ec = errcode.ErrCodeHvyogoSrvFail
|
||||
return
|
||||
}
|
||||
|
||||
// 5.组装返回结果
|
||||
data = &hvyogoproto.ApiQueryAgreeStateData{}
|
||||
deepcopier.Copy(resp).To(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) ApiHvyogoSingleDistribute(ctx *gin.Context, req *hvyogoproto.ApiSingleDistributeReq) (data *hvyogoproto.ApiSingleDistributeData, ec errcode.ErrCode) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"service/api/consts"
|
||||
"service/library/logger"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
var DefaultEsbService *EsbService
|
||||
|
||||
type EsbService struct {
|
||||
RoutingTable map[string]string
|
||||
}
|
||||
|
@ -9,10 +21,57 @@ func NewEsbService() *EsbService {
|
|||
}
|
||||
|
||||
func (s *EsbService) Init() (err error) {
|
||||
// 初始化路由表
|
||||
s.RoutingTable = make(map[string]string)
|
||||
|
||||
fullPath := consts.EsbRoutingTableConfig
|
||||
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromFile(fullPath); err != nil {
|
||||
logger.Error("read xml failed:%v.", err)
|
||||
}
|
||||
root := doc.SelectElement("esb_routing_table")
|
||||
if root == nil {
|
||||
logger.Error("read node esb_routing_table failed")
|
||||
}
|
||||
msgs := root.FindElements("./msg")
|
||||
if msgs == nil {
|
||||
logger.Error("read node msg failed")
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
name := msg.SelectAttrValue("name", "")
|
||||
url := msg.Text()
|
||||
s.RoutingTable[name] = url
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
func (s *EsbService) SendMsg(msgNo string, msg string) (err error) {
|
||||
func (s *EsbService) SendMsg(msgNo string, msg []byte) (resp []byte, err error) {
|
||||
|
||||
// 取路由地址
|
||||
url := s.RoutingTable[msgNo]
|
||||
|
||||
// 组装请求报文
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(msg))
|
||||
if err != nil {
|
||||
logger.Error("http NewRequest fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := http.DefaultClient
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Error("client Do fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
resp, err = io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
logger.Error("response read fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,3 +1,142 @@
|
|||
package service
|
||||
|
||||
func (s *Service)
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"service/api/message/request"
|
||||
"service/api/message/response"
|
||||
"service/bizcommon/util"
|
||||
"service/library/logger"
|
||||
"service/library/mycrypto"
|
||||
"time"
|
||||
|
||||
hvyogoproto "service/api/proto/hvyogo/proto"
|
||||
)
|
||||
|
||||
var DefaultHvyogoService *HvyogoService
|
||||
|
||||
type HvyogoService struct{}
|
||||
|
||||
func NewHvyogoService() *HvyogoService {
|
||||
return new(HvyogoService)
|
||||
}
|
||||
|
||||
func (s *HvyogoService) WorkerAgreeState(req *request.HYG10000001Req) (vo *hvyogoproto.WorkerAgreeStateVO, err error) {
|
||||
// 写参
|
||||
req.Timestamp = fmt.Sprint(time.Now().Unix())
|
||||
|
||||
// 加签
|
||||
msg, err := DefaultService.utilSignHvyogoMessage(req)
|
||||
if err != nil {
|
||||
logger.Error("utilSignHvyogoMessage fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
resp, err := DefaultEsbService.SendMsg("HYG10000001", msg)
|
||||
if err != nil {
|
||||
logger.Error("SendMsg fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理返回数据
|
||||
vo = &hvyogoproto.WorkerAgreeStateVO{}
|
||||
err = afterReceiving(resp, vo)
|
||||
if err != nil {
|
||||
logger.Error("afterReceiving fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HvyogoService) WorkerFindDetail(req *request.HYG10000002Req) (vo *hvyogoproto.WorkerFindDetailsVO, err error) {
|
||||
// 写参
|
||||
req.Timestamp = fmt.Sprint(time.Now().Unix())
|
||||
|
||||
// 加签
|
||||
msg, err := DefaultService.utilSignHvyogoMessage(req)
|
||||
if err != nil {
|
||||
logger.Error("utilSignHvyogoMessage fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
resp, err := DefaultEsbService.SendMsg("HYG10000002", msg)
|
||||
if err != nil {
|
||||
logger.Error("SendMsg fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理返回数据
|
||||
vo = &hvyogoproto.WorkerFindDetailsVO{}
|
||||
err = afterReceiving(resp, vo)
|
||||
if err != nil {
|
||||
logger.Error("afterReceiving fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HvyogoService) SingleDistribute(req *request.HYG10010001Req) (vo *hvyogoproto.SingleDistributeVO, err error) {
|
||||
// 写参
|
||||
req.Timestamp = fmt.Sprint(time.Now().Unix())
|
||||
|
||||
// 加签
|
||||
msg, err := DefaultService.utilSignHvyogoMessage(req)
|
||||
if err != nil {
|
||||
logger.Error("utilSignHvyogoMessage fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
resp, err := DefaultEsbService.SendMsg("HYG10010001", msg)
|
||||
if err != nil {
|
||||
logger.Error("SendMsg fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理返回数据
|
||||
vo = &hvyogoproto.SingleDistributeVO{}
|
||||
err = afterReceiving(resp, vo)
|
||||
if err != nil {
|
||||
logger.Error("afterReceiving fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func afterReceiving(resp []byte, vo any) (err error) {
|
||||
baseResponse := &response.HygBaseResponse{}
|
||||
err = json.Unmarshal(resp, baseResponse)
|
||||
if err != nil {
|
||||
logger.Error("json Unmarshal fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
aesEncryptedStr, ok := baseResponse.Data.(string)
|
||||
if !ok {
|
||||
err = fmt.Errorf("type assertion fail")
|
||||
logger.Error("Type assertion fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解码aes
|
||||
bytes, err := mycrypto.CryptoServiceInstance().HygAES.Decrypt([]byte(aesEncryptedStr))
|
||||
if err != nil {
|
||||
logger.Error("Decryption fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析至VO
|
||||
err = json.Unmarshal(bytes, vo)
|
||||
if err != nil {
|
||||
logger.Error("json Unmarshal fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(resp, vo)
|
||||
if err != nil {
|
||||
logger.Error("json Unmarshal fail, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4114,11 +4114,11 @@ func (s *Service) OpGetWorkerIdByMid(ctx *gin.Context, req *workeridproto.OpList
|
|||
return
|
||||
}
|
||||
|
||||
func (s *Service) SaveHvyogoCallback(ctx *gin.Context, req *hvyogoproto.HvyogoCallbackReq) (ec errcode.ErrCode) {
|
||||
func (s *Service) SaveAgreeCallback(ctx *gin.Context, req *hvyogoproto.ExtAgreeCallbackReq) (ec errcode.ErrCode) {
|
||||
ec = errcode.ErrCodeWorkerIdSrvOk
|
||||
|
||||
// 解析BusinessBody
|
||||
vo := &hvyogoproto.HvyogoVO{}
|
||||
vo := &hvyogoproto.ExtAgreeCallbackVO{}
|
||||
if err := json.Unmarshal([]byte(req.BusinessBody), vo); err != nil {
|
||||
logger.Error("Unmarshal fail, req: %v, err: %v", util.ToJson(req), err)
|
||||
ec = errcode.ErrCodeAssertionFail
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"service/api/consts"
|
||||
"service/api/errcode"
|
||||
"service/api/interfaces"
|
||||
"service/api/message/request"
|
||||
accountproto "service/api/proto/account/proto"
|
||||
accountrelationproto "service/api/proto/accountrelation/proto"
|
||||
contact_customer_service_sessionproto "service/api/proto/contact_customer_service_session/proto"
|
||||
|
@ -28,6 +30,7 @@ import (
|
|||
"service/dbstruct"
|
||||
"service/library/apollo"
|
||||
"service/library/logger"
|
||||
"service/library/mycrypto"
|
||||
"service/library/redis"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -2091,3 +2094,38 @@ func (s *Service) utilAssembleDailyStatementZoneInfo(zoneprofits, zonerefunds []
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) utilSignHvyogoMessage(msg interfaces.HvyogoSignable) ([]byte, error) {
|
||||
// 1.RSA加密生成签名
|
||||
msg.GetArgString()
|
||||
sign := ""
|
||||
|
||||
// 2.设置签名
|
||||
msg.SetSign(sign)
|
||||
|
||||
// 3.AES加密
|
||||
bytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
logger.Error("json Marshal fail, err :%v", err)
|
||||
return make([]byte, 0), err
|
||||
}
|
||||
aesEncryptedBytes, err := mycrypto.CryptoServiceInstance().HygAES.Encrypt(bytes)
|
||||
if err != nil {
|
||||
logger.Error("HygAES Encryption fail, err :%v", err)
|
||||
return make([]byte, 0), err
|
||||
}
|
||||
|
||||
// 4.生成最终报文
|
||||
result := &request.HYGCommonReq{
|
||||
CooperatorId: msg.GetCooperatorId(),
|
||||
BusinessBody: string(aesEncryptedBytes),
|
||||
}
|
||||
|
||||
resultBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
logger.Error("json Marshal fail, err :%v", err)
|
||||
return make([]byte, 0), err
|
||||
}
|
||||
|
||||
return resultBytes, nil
|
||||
}
|
||||
|
|
|
@ -7,5 +7,11 @@ type WorkerId struct {
|
|||
Ct *int64 `json:"ct" bson:"ct"` // 创建时间
|
||||
Ut *int64 `json:"ut" bson:"ut"` // 更新时间
|
||||
DelFlag *int64 `json:"del_flag" bson:"del_flag"` // 删除标记
|
||||
|
||||
}
|
||||
|
||||
func (p *WorkerId) GetWorkerId() string {
|
||||
if p == nil || p.WorkerId == nil {
|
||||
return ""
|
||||
}
|
||||
return *p.WorkerId
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ app:
|
|||
port: 9999
|
||||
|
||||
mix_mongo:
|
||||
uri: "mongodb://127.0.0.1:27017"
|
||||
uri: "mongodb://admin:Wishpal%402023@127.0.0.1:27017"
|
||||
username: ""
|
||||
password: ""
|
||||
max_pool_size: 16
|
||||
|
@ -22,7 +22,7 @@ mix_mongo:
|
|||
mix_mysql:
|
||||
uri: "127.0.0.1:3306"
|
||||
username: "root"
|
||||
password: "11111111"
|
||||
password: "Wishpal@2023"
|
||||
timeout: 3
|
||||
read_timeout_s: 5
|
||||
write_timeout_s: 3
|
||||
|
@ -98,6 +98,10 @@ redis:
|
|||
|
||||
server_info:
|
||||
file_server_domain_name: "https://file.wishpaldev.tech/"
|
||||
main_server_ips: "101.37.77.228"
|
||||
main_server_ports: "9999"
|
||||
main_server_users: "root"
|
||||
main_server_passwords: "Wishpal@2024"
|
||||
|
||||
xxl_job:
|
||||
server_addr: "http://127.0.0.1:9800/xxl-job-admin"
|
||||
|
@ -107,6 +111,16 @@ xxl_job:
|
|||
registry_key: "golang-jobs-executor"
|
||||
log_path: "/Users/erwin/log/wishpal-ironfan/"
|
||||
|
||||
ding_talk_robot:
|
||||
access_token: "65f11ade605568c34f33ff79ed4c20c1721bd6da18511d8e08a6ed9666c4b8b4"
|
||||
secret: "SECcc49257b2681b6488ab4be8ee815c61027a7254e2be3239c229de38b22207b5b"
|
||||
|
||||
elastic_search:
|
||||
uri: "http://127.0.0.1:9200"
|
||||
username: "elastic"
|
||||
password: "Wishpal@2024"
|
||||
sniff: false
|
||||
|
||||
sd:
|
||||
host: "https://consul.tiefen.fun"
|
||||
service_name: "local_test"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<esb_routing_table>
|
||||
<msg name = "HYG10000001"></msg>
|
||||
<msg name = "HYG10000002"></msg>
|
||||
<msg name = "HYG10010001"></msg>
|
||||
</esb_routing_table>
|
1
go.mod
1
go.mod
|
@ -117,6 +117,7 @@ require (
|
|||
github.com/stretchr/testify v1.9.0
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -546,6 +546,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA=
|
||||
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,4 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.13
|
||||
script: make test
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Ulule
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.PHONY: test
|
||||
test:
|
||||
cd tests; go test -race
|
||||
cd tests; go test -cover
|
||||
cd tests; go test -v
|
|
@ -0,0 +1,129 @@
|
|||
# Deepcopier
|
||||
|
||||
[](http://travis-ci.org/ulule/deepcopier)
|
||||
|
||||
This package is meant to make copying of structs to/from others structs a bit easier.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get -u github.com/ulule/deepcopier
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```golang
|
||||
// Deep copy instance1 into instance2
|
||||
Copy(instance1).To(instance2)
|
||||
|
||||
// Deep copy instance1 into instance2 and passes the following context (which
|
||||
// is basically a map[string]interface{}) as first argument
|
||||
// to methods of instance2 that defined the struct tag "context".
|
||||
Copy(instance1).WithContext(map[string]interface{}{"foo": "bar"}).To(instance2)
|
||||
|
||||
// Deep copy instance2 into instance1
|
||||
Copy(instance1).From(instance2)
|
||||
|
||||
// Deep copy instance2 into instance1 and passes the following context (which
|
||||
// is basically a map[string]interface{}) as first argument
|
||||
// to methods of instance1 that defined the struct tag "context".
|
||||
Copy(instance1).WithContext(map[string]interface{}{"foo": "bar"}).From(instance2)
|
||||
```
|
||||
|
||||
Available options for `deepcopier` struct tag:
|
||||
|
||||
| Option | Description |
|
||||
| --------- | -------------------------------------------------------------------- |
|
||||
| `field` | Field or method name in source instance |
|
||||
| `skip` | Ignores the field |
|
||||
| `context` | Takes a `map[string]interface{}` as first argument (for methods) |
|
||||
| `force` | Set the value of a `sql.Null*` field (instead of copying the struct) |
|
||||
|
||||
**Options example:**
|
||||
|
||||
```golang
|
||||
type Source struct {
|
||||
Name string
|
||||
SkipMe string
|
||||
SQLNullStringToSQLNullString sql.NullString
|
||||
SQLNullStringToString sql.NullString
|
||||
|
||||
}
|
||||
|
||||
func (Source) MethodThatTakesContext(c map[string]interface{}) string {
|
||||
return "whatever"
|
||||
}
|
||||
|
||||
type Destination struct {
|
||||
FieldWithAnotherNameInSource string `deepcopier:"field:Name"`
|
||||
SkipMe string `deepcopier:"skip"`
|
||||
MethodThatTakesContext string `deepcopier:"context"`
|
||||
SQLNullStringToSQLNullString sql.NullString
|
||||
SQLNullStringToString string `deepcopier:"force"`
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
// Model
|
||||
type User struct {
|
||||
// Basic string field
|
||||
Name string
|
||||
// Deepcopier supports https://golang.org/pkg/database/sql/driver/#Valuer
|
||||
Email sql.NullString
|
||||
}
|
||||
|
||||
func (u *User) MethodThatTakesContext(ctx map[string]interface{}) string {
|
||||
// do whatever you want
|
||||
return "hello from this method"
|
||||
}
|
||||
|
||||
// Resource
|
||||
type UserResource struct {
|
||||
DisplayName string `deepcopier:"field:Name"`
|
||||
SkipMe string `deepcopier:"skip"`
|
||||
MethodThatTakesContext string `deepcopier:"context"`
|
||||
Email string `deepcopier:"force"`
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
user := &User{
|
||||
Name: "gilles",
|
||||
Email: sql.NullString{
|
||||
Valid: true,
|
||||
String: "gilles@example.com",
|
||||
},
|
||||
}
|
||||
|
||||
resource := &UserResource{}
|
||||
|
||||
deepcopier.Copy(user).To(resource)
|
||||
|
||||
fmt.Println(resource.DisplayName)
|
||||
fmt.Println(resource.Email)
|
||||
}
|
||||
```
|
||||
|
||||
Looking for more information about the usage?
|
||||
|
||||
We wrote [an introduction article](https://github.com/ulule/deepcopier/blob/master/examples/rest-usage/README.rst).
|
||||
Have a look and feel free to give us your feedback.
|
||||
|
||||
## Contributing
|
||||
|
||||
* Ping us on twitter [@oibafsellig](https://twitter.com/oibafsellig), [@thoas](https://twitter.com/thoas)
|
||||
* Fork the [project](https://github.com/ulule/deepcopier)
|
||||
* Help us improving and fixing [issues](https://github.com/ulule/deepcopier/issues)
|
||||
|
||||
Don't hesitate ;)
|
|
@ -0,0 +1,362 @@
|
|||
package deepcopier
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// TagName is the deepcopier struct tag name.
|
||||
TagName = "deepcopier"
|
||||
// FieldOptionName is the from field option name for struct tag.
|
||||
FieldOptionName = "field"
|
||||
// ContextOptionName is the context option name for struct tag.
|
||||
ContextOptionName = "context"
|
||||
// SkipOptionName is the skip option name for struct tag.
|
||||
SkipOptionName = "skip"
|
||||
// ForceOptionName is the skip option name for struct tag.
|
||||
ForceOptionName = "force"
|
||||
)
|
||||
|
||||
type (
|
||||
// TagOptions is a map that contains extracted struct tag options.
|
||||
TagOptions map[string]string
|
||||
|
||||
// Options are copier options.
|
||||
Options struct {
|
||||
// Context given to WithContext() method.
|
||||
Context map[string]interface{}
|
||||
// Reversed reverses struct tag checkings.
|
||||
Reversed bool
|
||||
}
|
||||
)
|
||||
|
||||
// DeepCopier deep copies a struct to/from a struct.
|
||||
type DeepCopier struct {
|
||||
dst interface{}
|
||||
src interface{}
|
||||
ctx map[string]interface{}
|
||||
}
|
||||
|
||||
// Copy sets source or destination.
|
||||
func Copy(src interface{}) *DeepCopier {
|
||||
return &DeepCopier{src: src}
|
||||
}
|
||||
|
||||
// WithContext injects the given context into the builder instance.
|
||||
func (dc *DeepCopier) WithContext(ctx map[string]interface{}) *DeepCopier {
|
||||
dc.ctx = ctx
|
||||
return dc
|
||||
}
|
||||
|
||||
// To sets the destination.
|
||||
func (dc *DeepCopier) To(dst interface{}) error {
|
||||
dc.dst = dst
|
||||
return process(dc.dst, dc.src, Options{Context: dc.ctx})
|
||||
}
|
||||
|
||||
// From sets the given the source as destination and destination as source.
|
||||
func (dc *DeepCopier) From(src interface{}) error {
|
||||
dc.dst = dc.src
|
||||
dc.src = src
|
||||
return process(dc.dst, dc.src, Options{Context: dc.ctx, Reversed: true})
|
||||
}
|
||||
|
||||
// process handles copy.
|
||||
func process(dst interface{}, src interface{}, args ...Options) error {
|
||||
var (
|
||||
options = Options{}
|
||||
srcValue = reflect.Indirect(reflect.ValueOf(src))
|
||||
dstValue = reflect.Indirect(reflect.ValueOf(dst))
|
||||
srcFieldNames = getFieldNames(src)
|
||||
srcMethodNames = getMethodNames(src)
|
||||
)
|
||||
|
||||
if len(args) > 0 {
|
||||
options = args[0]
|
||||
}
|
||||
|
||||
if !dstValue.CanAddr() {
|
||||
return fmt.Errorf("destination %+v is unaddressable", dstValue.Interface())
|
||||
}
|
||||
|
||||
for _, f := range srcFieldNames {
|
||||
var (
|
||||
srcFieldValue = srcValue.FieldByName(f)
|
||||
srcFieldType, srcFieldFound = srcValue.Type().FieldByName(f)
|
||||
srcFieldName = srcFieldType.Name
|
||||
dstFieldName = srcFieldName
|
||||
tagOptions TagOptions
|
||||
)
|
||||
|
||||
if !srcFieldFound {
|
||||
continue
|
||||
}
|
||||
|
||||
if options.Reversed {
|
||||
tagOptions = getTagOptions(srcFieldType.Tag.Get(TagName))
|
||||
if v, ok := tagOptions[FieldOptionName]; ok && v != "" {
|
||||
dstFieldName = v
|
||||
}
|
||||
} else {
|
||||
if name, opts := getRelatedField(dst, srcFieldName); name != "" {
|
||||
dstFieldName, tagOptions = name, opts
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := tagOptions[SkipOptionName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
dstFieldType, dstFieldFound = dstValue.Type().FieldByName(dstFieldName)
|
||||
dstFieldValue = dstValue.FieldByName(dstFieldName)
|
||||
)
|
||||
|
||||
if !dstFieldFound {
|
||||
continue
|
||||
}
|
||||
|
||||
// Force option for empty interfaces and nullable types
|
||||
_, force := tagOptions[ForceOptionName]
|
||||
|
||||
// Valuer -> ptr
|
||||
if isNullableType(srcFieldType.Type) && dstFieldValue.Kind() == reflect.Ptr && force {
|
||||
// We have same nullable type on both sides
|
||||
if srcFieldValue.Type().AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(srcFieldValue)
|
||||
continue
|
||||
}
|
||||
|
||||
v, _ := srcFieldValue.Interface().(driver.Valuer).Value()
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
valueType := reflect.TypeOf(v)
|
||||
|
||||
ptr := reflect.New(valueType)
|
||||
ptr.Elem().Set(reflect.ValueOf(v))
|
||||
|
||||
if valueType.AssignableTo(dstFieldType.Type.Elem()) {
|
||||
dstFieldValue.Set(ptr)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Valuer -> value
|
||||
if isNullableType(srcFieldType.Type) {
|
||||
// We have same nullable type on both sides
|
||||
if srcFieldValue.Type().AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(srcFieldValue)
|
||||
continue
|
||||
}
|
||||
|
||||
if force {
|
||||
v, _ := srcFieldValue.Interface().(driver.Valuer).Value()
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Type().AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(rv)
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if dstFieldValue.Kind() == reflect.Interface {
|
||||
if force {
|
||||
dstFieldValue.Set(srcFieldValue)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Ptr -> Value
|
||||
if srcFieldType.Type.Kind() == reflect.Ptr && !srcFieldValue.IsNil() && dstFieldType.Type.Kind() != reflect.Ptr {
|
||||
indirect := reflect.Indirect(srcFieldValue)
|
||||
|
||||
if indirect.Type().AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(indirect)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Other types
|
||||
if srcFieldType.Type.AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(srcFieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range srcMethodNames {
|
||||
name, opts := getRelatedField(dst, m)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := opts[SkipOptionName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
method := reflect.ValueOf(src).MethodByName(m)
|
||||
if !method.IsValid() {
|
||||
return fmt.Errorf("method %s is invalid", m)
|
||||
}
|
||||
|
||||
var (
|
||||
dstFieldType, _ = dstValue.Type().FieldByName(name)
|
||||
dstFieldValue = dstValue.FieldByName(name)
|
||||
_, withContext = opts[ContextOptionName]
|
||||
_, force = opts[ForceOptionName]
|
||||
)
|
||||
|
||||
args := []reflect.Value{}
|
||||
if withContext {
|
||||
args = []reflect.Value{reflect.ValueOf(options.Context)}
|
||||
}
|
||||
|
||||
var (
|
||||
result = method.Call(args)[0]
|
||||
resultInterface = result.Interface()
|
||||
resultValue = reflect.ValueOf(resultInterface)
|
||||
resultType = resultValue.Type()
|
||||
)
|
||||
|
||||
// Value -> Ptr
|
||||
if dstFieldValue.Kind() == reflect.Ptr && force {
|
||||
ptr := reflect.New(resultType)
|
||||
ptr.Elem().Set(resultValue)
|
||||
|
||||
if ptr.Type().AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(ptr)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ptr -> value
|
||||
if resultValue.Kind() == reflect.Ptr && force {
|
||||
if resultValue.Elem().Type().AssignableTo(dstFieldType.Type) {
|
||||
dstFieldValue.Set(resultValue.Elem())
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if resultType.AssignableTo(dstFieldType.Type) && result.IsValid() {
|
||||
dstFieldValue.Set(result)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTagOptions parses deepcopier tag field and returns options.
|
||||
func getTagOptions(value string) TagOptions {
|
||||
options := TagOptions{}
|
||||
|
||||
for _, opt := range strings.Split(value, ";") {
|
||||
o := strings.Split(opt, ":")
|
||||
|
||||
// deepcopier:"keyword; without; value;"
|
||||
if len(o) == 1 {
|
||||
options[o[0]] = ""
|
||||
}
|
||||
|
||||
// deepcopier:"key:value; anotherkey:anothervalue"
|
||||
if len(o) == 2 {
|
||||
options[strings.TrimSpace(o[0])] = strings.TrimSpace(o[1])
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// getRelatedField returns first matching field.
|
||||
func getRelatedField(instance interface{}, name string) (string, TagOptions) {
|
||||
var (
|
||||
value = reflect.Indirect(reflect.ValueOf(instance))
|
||||
fieldName string
|
||||
tagOptions TagOptions
|
||||
)
|
||||
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
var (
|
||||
vField = value.Field(i)
|
||||
tField = value.Type().Field(i)
|
||||
tagOptions = getTagOptions(tField.Tag.Get(TagName))
|
||||
)
|
||||
|
||||
if tField.Type.Kind() == reflect.Struct && tField.Anonymous {
|
||||
if n, o := getRelatedField(vField.Interface(), name); n != "" {
|
||||
return n, o
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := tagOptions[FieldOptionName]; ok && v == name {
|
||||
return tField.Name, tagOptions
|
||||
}
|
||||
|
||||
if tField.Name == name {
|
||||
return tField.Name, tagOptions
|
||||
}
|
||||
}
|
||||
|
||||
return fieldName, tagOptions
|
||||
}
|
||||
|
||||
// getMethodNames returns instance's method names.
|
||||
func getMethodNames(instance interface{}) []string {
|
||||
var methods []string
|
||||
|
||||
t := reflect.TypeOf(instance)
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
methods = append(methods, t.Method(i).Name)
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
// getFieldNames returns instance's field names.
|
||||
func getFieldNames(instance interface{}) []string {
|
||||
var (
|
||||
fields []string
|
||||
v = reflect.Indirect(reflect.ValueOf(instance))
|
||||
t = v.Type()
|
||||
)
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
var (
|
||||
vField = v.Field(i)
|
||||
tField = v.Type().Field(i)
|
||||
)
|
||||
|
||||
// Is exportable?
|
||||
if tField.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if tField.Type.Kind() == reflect.Struct && tField.Anonymous {
|
||||
fields = append(fields, getFieldNames(vField.Interface())...)
|
||||
continue
|
||||
}
|
||||
|
||||
fields = append(fields, tField.Name)
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// isNullableType returns true if the given type is a nullable one.
|
||||
func isNullableType(t reflect.Type) bool {
|
||||
return t.ConvertibleTo(reflect.TypeOf((*driver.Valuer)(nil)).Elem())
|
||||
}
|
|
@ -430,6 +430,9 @@ github.com/twitchyliquid64/golang-asm/unsafeheader
|
|||
# github.com/ugorji/go/codec v1.2.12
|
||||
## explicit; go 1.11
|
||||
github.com/ugorji/go/codec
|
||||
# github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
|
||||
## explicit; go 1.14
|
||||
github.com/ulule/deepcopier
|
||||
# github.com/xdg-go/pbkdf2 v1.0.0
|
||||
## explicit; go 1.9
|
||||
github.com/xdg-go/pbkdf2
|
||||
|
|
Loading…
Reference in New Issue