diff --git a/app/main/api/__debug_bin4091993710.exe b/app/main/api/__debug_bin4091993710.exe new file mode 100644 index 0000000..1fdd108 Binary files /dev/null and b/app/main/api/__debug_bin4091993710.exe differ diff --git a/app/main/api/desc/front/agent.api b/app/main/api/desc/front/agent.api index b0ae6c0..fb26380 100644 --- a/app/main/api/desc/front/agent.api +++ b/app/main/api/desc/front/agent.api @@ -7,6 +7,23 @@ info ( email: "2440983361@qq.com" version: "v1" ) +@server ( + prefix: api/v1/agent + group: agent +) +service main { + // 获取推广二维码海报 + @handler GetAgentPromotionQrcode + get /promotion/qrcode (GetAgentPromotionQrcodeReq) + +} + +type ( + GetAgentPromotionQrcodeReq{ + QrcodeType string `form:"qrcode_type"` + QrcodeUrl string `form:"qrcode_url"` + } +) // 代理服务基本类型定义 type AgentProductConfig { @@ -35,7 +52,6 @@ type ProductConfig { PriceRangeMin float64 `json:"price_range_min"` PriceRangeMax float64 `json:"price_range_max"` } - @server ( prefix: api/v1/agent group: agent @@ -46,6 +62,17 @@ service main { @handler GetAgentInfo get /info returns (AgentInfoResp) + @handler GetAgentRevenueInfo + get /revenue (GetAgentRevenueInfoReq) returns (GetAgentRevenueInfoResp) +} + +@server ( + prefix: api/v1/agent + group: agent + jwt: JwtAuth + middleware: UserAuthInterceptor +) +service main { // 查询代理申请状态 @handler GetAgentAuditStatus get /audit/status returns (AgentAuditStatusResp) @@ -93,6 +120,7 @@ type ( AgentGeneratingLinkResp { LinkIdentifier string `json:"link_identifier"` } + AgentProductConfigResp { AgentProductConfig []AgentProductConfig } @@ -168,6 +196,7 @@ type ( prefix: api/v1/agent group: agent jwt: JwtAuth + middleware: UserAuthInterceptor ) service main { @handler GetAgentMembershipProductConfig @@ -203,10 +232,9 @@ type ( prefix: api/v1/agent group: agent jwt: JwtAuth + middleware: UserAuthInterceptor ) service main { - @handler GetAgentRevenueInfo - get /revenue (GetAgentRevenueInfoReq) returns (GetAgentRevenueInfoResp) @handler GetAgentCommission get /commission (GetCommissionReq) returns (GetCommissionResp) @@ -326,6 +354,7 @@ type ( @server ( prefix: api/v1/agent group: agent + middleware: AuthInterceptor ) service main { // 提交代理申请 @@ -335,7 +364,6 @@ service main { // 获取推广标识数据 @handler GetLinkData get /link (GetLinkDataReq) returns (GetLinkDataResp) - } type ( diff --git a/app/main/api/desc/front/pay.api b/app/main/api/desc/front/pay.api index 4ade5c2..08ca89d 100644 --- a/app/main/api/desc/front/pay.api +++ b/app/main/api/desc/front/pay.api @@ -30,7 +30,7 @@ service main { prefix: api/v1 group: pay jwt: JwtAuth - middleware: SourceInterceptor + middleware: UserAuthInterceptor ) service main { // 支付 diff --git a/app/main/api/desc/front/product.api b/app/main/api/desc/front/product.api index 9fb20f3..10baded 100644 --- a/app/main/api/desc/front/product.api +++ b/app/main/api/desc/front/product.api @@ -26,6 +26,7 @@ type Product { prefix: api/v1/product group: product jwt: JwtAuth + middleware: UserAuthInterceptor ) service main { @handler GetProductByID diff --git a/app/main/api/desc/front/query.api b/app/main/api/desc/front/query.api index ceca631..356e456 100644 --- a/app/main/api/desc/front/query.api +++ b/app/main/api/desc/front/query.api @@ -75,6 +75,7 @@ type ( prefix: api/v1 group: query jwt: JwtAuth + middleware: UserAuthInterceptor ) service main { @doc "query service" @@ -86,6 +87,7 @@ service main { prefix: api/v1 group: query jwt: JwtAuth + middleware: UserAuthInterceptor ) service main { @doc "获取查询临时订单" diff --git a/app/main/api/desc/front/user.api b/app/main/api/desc/front/user.api index 14f080e..3f0f818 100644 --- a/app/main/api/desc/front/user.api +++ b/app/main/api/desc/front/user.api @@ -14,6 +14,7 @@ type User { Id int64 `json:"id"` Mobile string `json:"mobile"` NickName string `json:"nickName"` + UserType int64 `json:"userType"` } //no need login @@ -22,22 +23,10 @@ type User { group: user ) service main { - @doc "register" - @handler register - post /user/register (RegisterReq) returns (RegisterResp) - - @doc "mobile login" - @handler mobileLogin - post /user/mobileLogin (MobileLoginReq) returns (MobileLoginResp) - @doc "mobile code login" @handler mobileCodeLogin post /user/mobileCodeLogin (MobileCodeLoginReq) returns (MobileCodeLoginResp) - @doc "agent mobile code login" - @handler agentMobileCodeLogin - post /user/agent_mobile_code_login (MobileCodeLoginReq) returns (MobileCodeLoginResp) - @doc "wechat mini auth" @handler wxMiniAuth post /user/wxMiniAuth (WXMiniAuthReq) returns (WXMiniAuthResp) @@ -47,31 +36,6 @@ service main { post /user/wxh5Auth (WXH5AuthReq) returns (WXH5AuthResp) } -type ( - RegisterReq { - Mobile string `json:"mobile" validate:"required,mobile"` - Password string `json:"password" validate:"required,min=11,max=11,password"` - Code string `json:"code" validate:"required"` - } - RegisterResp { - AccessToken string `json:"accessToken"` - AccessExpire int64 `json:"accessExpire"` - RefreshAfter int64 `json:"refreshAfter"` - } -) - -type ( - MobileLoginReq { - Mobile string `json:"mobile" validate:"required,mobile"` - Password string `json:"password" validate:"required"` - } - MobileLoginResp { - AccessToken string `json:"accessToken"` - AccessExpire int64 `json:"accessExpire"` - RefreshAfter int64 `json:"refreshAfter"` - } -) - type ( MobileCodeLoginReq { Mobile string `json:"mobile"` @@ -86,9 +50,7 @@ type ( type ( WXMiniAuthReq { - Code string `json:"code"` - IV string `json:"iv"` - EncryptedData string `json:"encryptedData"` + Code string `json:"code"` } WXMiniAuthResp { AccessToken string `json:"accessToken"` @@ -107,6 +69,17 @@ type ( RefreshAfter int64 `json:"refreshAfter"` } ) +//need login +@server ( + prefix: api/v1 + group: user + middleware: AuthInterceptor +) +service main { + @doc "绑定手机号" + @handler bindMobile + post /user/bindMobile (BindMobileReq) returns (BindMobileResp) +} //need login @server ( @@ -126,9 +99,6 @@ service main { @handler cancelOut post /user/cancelOut - @doc "绑定手机号" - @handler bindMobile - post /user/bindMobile (BindMobileReq) returns (BindMobileResp) } type ( @@ -141,6 +111,9 @@ type ( Code string `json:"code" validate:"required"` } BindMobileResp { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` } ) diff --git a/app/main/api/etc/main.dev.yaml b/app/main/api/etc/main.dev.yaml index e5b0143..35c450c 100644 --- a/app/main/api/etc/main.dev.yaml +++ b/app/main/api/etc/main.dev.yaml @@ -65,6 +65,9 @@ SystemConfig: WechatH5: AppID: "wx442ee1ac1ee75917" AppSecret: "c80474909db42f63913b7a307b3bee17" +WechatMini: + AppID: "wx5bacc94add2da981" # 小程序的AppID + AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret CloudAuth: AccessKeyId: "LTAI5tSWnaq1kvUawsV4ayL8" AccessKeySecret: "fSGdUzm4TGtkus9rUVzipSOhEtimG5" diff --git a/app/main/api/etc/main.yaml b/app/main/api/etc/main.yaml index 15157e7..50e25e2 100644 --- a/app/main/api/etc/main.yaml +++ b/app/main/api/etc/main.yaml @@ -66,6 +66,9 @@ SystemConfig: WechatH5: AppID: "wx442ee1ac1ee75917" AppSecret: "c80474909db42f63913b7a307b3bee17" +WechatMini: + AppID: "wx5bacc94add2da981" # 小程序的AppID + AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret CloudAuth: AccessKeyId: "LTAI5tSWnaq1kvUawsV4ayL8" AccessKeySecret: "fSGdUzm4TGtkus9rUVzipSOhEtimG5" diff --git a/app/main/api/internal/config/config.go b/app/main/api/internal/config/config.go index 2cc6ef2..c2055b7 100644 --- a/app/main/api/internal/config/config.go +++ b/app/main/api/internal/config/config.go @@ -20,6 +20,7 @@ type Config struct { YushanConfig YushanConfig SystemConfig SystemConfig WechatH5 WechatH5Config + WechatMini WechatMiniConfig // 添加小程序配置 CloudAuth CloudAuthConfig Query QueryConfig AdminConfig AdminConfig @@ -97,6 +98,13 @@ type WechatH5Config struct { AppID string AppSecret string } + +// WechatMiniConfig 小程序配置 +type WechatMiniConfig struct { + AppID string + AppSecret string +} + type CloudAuthConfig struct { AccessKeyId string AccessKeySecret string diff --git a/app/main/api/internal/handler/agent/getagentpromotionqrcodehandler.go b/app/main/api/internal/handler/agent/getagentpromotionqrcodehandler.go new file mode 100644 index 0000000..d80dd55 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentpromotionqrcodehandler.go @@ -0,0 +1,36 @@ +package agent + +import ( + "net/http" + + "qnc-server/app/main/api/internal/logic/agent" + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/result" + "qnc-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentPromotionQrcodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAgentPromotionQrcodeReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + + // 注意:这里传入了 ResponseWriter,用于直接写入图片数据 + l := agent.NewGetAgentPromotionQrcodeLogic(r.Context(), svcCtx, w) + err := l.GetAgentPromotionQrcode(&req) + if err != nil { + // 如果处理过程中出错,返回JSON错误响应 + result.HttpResult(r, w, nil, err) + } + // 成功时,图片数据已经通过logic直接写入ResponseWriter,不需要额外处理 + } +} diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go index 372018d..39301fc 100644 --- a/app/main/api/internal/handler/routes.go +++ b/app/main/api/internal/handler/routes.go @@ -518,111 +518,141 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { []rest.Route{ { Method: http.MethodGet, - Path: "/audit/status", - Handler: agent.GetAgentAuditStatusHandler(serverCtx), - }, - { - Method: http.MethodPost, - Path: "/generating_link", - Handler: agent.GeneratingLinkHandler(serverCtx), + Path: "/promotion/qrcode", + Handler: agent.GetAgentPromotionQrcodeHandler(serverCtx), }, + }, + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + []rest.Route{ { Method: http.MethodGet, Path: "/info", Handler: agent.GetAgentInfoHandler(serverCtx), }, - { - Method: http.MethodGet, - Path: "/product_config", - Handler: agent.GetAgentProductConfigHandler(serverCtx), - }, - { - Method: http.MethodPost, - Path: "/real_name", - Handler: agent.AgentRealNameHandler(serverCtx), - }, - { - Method: http.MethodGet, - Path: "/subordinate/contribution/detail", - Handler: agent.GetAgentSubordinateContributionDetailHandler(serverCtx), - }, - { - Method: http.MethodGet, - Path: "/subordinate/list", - Handler: agent.GetAgentSubordinateListHandler(serverCtx), - }, - }, - rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), - rest.WithPrefix("/api/v1/agent"), - ) - - server.AddRoutes( - []rest.Route{ - { - Method: http.MethodPost, - Path: "/membership/save_user_config", - Handler: agent.SaveAgentMembershipUserConfigHandler(serverCtx), - }, - { - Method: http.MethodGet, - Path: "/membership/user_config", - Handler: agent.GetAgentMembershipProductConfigHandler(serverCtx), - }, - }, - rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), - rest.WithPrefix("/api/v1/agent"), - ) - - server.AddRoutes( - []rest.Route{ - { - Method: http.MethodGet, - Path: "/commission", - Handler: agent.GetAgentCommissionHandler(serverCtx), - }, - { - Method: http.MethodPost, - Path: "/membership/activate", - Handler: agent.ActivateAgentMembershipHandler(serverCtx), - }, { Method: http.MethodGet, Path: "/revenue", Handler: agent.GetAgentRevenueInfoHandler(serverCtx), }, - { - Method: http.MethodGet, - Path: "/rewards", - Handler: agent.GetAgentRewardsHandler(serverCtx), - }, - { - Method: http.MethodGet, - Path: "/withdrawal", - Handler: agent.GetAgentWithdrawalHandler(serverCtx), - }, - { - Method: http.MethodPost, - Path: "/withdrawal", - Handler: agent.AgentWithdrawalHandler(serverCtx), - }, }, rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), rest.WithPrefix("/api/v1/agent"), ) server.AddRoutes( - []rest.Route{ - { - Method: http.MethodPost, - Path: "/apply", - Handler: agent.ApplyForAgentHandler(serverCtx), - }, - { - Method: http.MethodGet, - Path: "/link", - Handler: agent.GetLinkDataHandler(serverCtx), - }, - }, + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/audit/status", + Handler: agent.GetAgentAuditStatusHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/generating_link", + Handler: agent.GeneratingLinkHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/product_config", + Handler: agent.GetAgentProductConfigHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/real_name", + Handler: agent.AgentRealNameHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/subordinate/contribution/detail", + Handler: agent.GetAgentSubordinateContributionDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/subordinate/list", + Handler: agent.GetAgentSubordinateListHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/membership/save_user_config", + Handler: agent.SaveAgentMembershipUserConfigHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/membership/user_config", + Handler: agent.GetAgentMembershipProductConfigHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/commission", + Handler: agent.GetAgentCommissionHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/membership/activate", + Handler: agent.ActivateAgentMembershipHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/rewards", + Handler: agent.GetAgentRewardsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/withdrawal", + Handler: agent.GetAgentWithdrawalHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/withdrawal", + Handler: agent.AgentWithdrawalHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/apply", + Handler: agent.ApplyForAgentHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/link", + Handler: agent.GetLinkDataHandler(serverCtx), + }, + }..., + ), rest.WithPrefix("/api/v1/agent"), ) @@ -714,7 +744,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { server.AddRoutes( rest.WithMiddlewares( - []rest.Middleware{serverCtx.SourceInterceptor}, + []rest.Middleware{serverCtx.UserAuthInterceptor}, []rest.Route{ { Method: http.MethodPost, @@ -743,18 +773,21 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { ) server.AddRoutes( - []rest.Route{ - { - Method: http.MethodGet, - Path: "/:id", - Handler: product.GetProductByIDHandler(serverCtx), - }, - { - Method: http.MethodGet, - Path: "/en/:product_en", - Handler: product.GetProductByEnHandler(serverCtx), - }, - }, + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/:id", + Handler: product.GetProductByIDHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/en/:product_en", + Handler: product.GetProductByEnHandler(serverCtx), + }, + }..., + ), rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), rest.WithPrefix("/api/v1/product"), ) @@ -791,75 +824,81 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { ) server.AddRoutes( - []rest.Route{ - { - // query service - Method: http.MethodPost, - Path: "/query/service/:product", - Handler: query.QueryServiceHandler(serverCtx), - }, - }, + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + // query service + Method: http.MethodPost, + Path: "/query/service/:product", + Handler: query.QueryServiceHandler(serverCtx), + }, + }..., + ), rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), rest.WithPrefix("/api/v1"), ) server.AddRoutes( - []rest.Route{ - { - // 确认查询状态 - Method: http.MethodPost, - Path: "/query/confirm_state", - Handler: query.ConfirmQueryStateHandler(serverCtx), - }, - { - // 生成分享链接 - Method: http.MethodPost, - Path: "/query/generate_share_link", - Handler: query.QueryGenerateShareLinkHandler(serverCtx), - }, - { - // 查询列表 - Method: http.MethodGet, - Path: "/query/list", - Handler: query.QueryListHandler(serverCtx), - }, - { - // 查询详情 按订单号 付款查询时 - Method: http.MethodGet, - Path: "/query/orderId/:order_id", - Handler: query.QueryDetailByOrderIdHandler(serverCtx), - }, - { - // 查询详情 按订单号 - Method: http.MethodGet, - Path: "/query/orderNo/:order_no", - Handler: query.QueryDetailByOrderNoHandler(serverCtx), - }, - { - // 获取查询临时订单 - Method: http.MethodGet, - Path: "/query/provisional_order/:id", - Handler: query.QueryProvisionalOrderHandler(serverCtx), - }, - { - // 补查 - Method: http.MethodPost, - Path: "/query/recheck/:query_id", - Handler: query.QueryRecheckHandler(serverCtx), - }, - { - // 重试查询 - Method: http.MethodPost, - Path: "/query/retry/:id", - Handler: query.QueryRetryHandler(serverCtx), - }, - { - // 更新查询数据 - Method: http.MethodPost, - Path: "/query/update_data", - Handler: query.UpdateQueryDataHandler(serverCtx), - }, - }, + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + // 确认查询状态 + Method: http.MethodPost, + Path: "/query/confirm_state", + Handler: query.ConfirmQueryStateHandler(serverCtx), + }, + { + // 生成分享链接 + Method: http.MethodPost, + Path: "/query/generate_share_link", + Handler: query.QueryGenerateShareLinkHandler(serverCtx), + }, + { + // 查询列表 + Method: http.MethodGet, + Path: "/query/list", + Handler: query.QueryListHandler(serverCtx), + }, + { + // 查询详情 按订单号 付款查询时 + Method: http.MethodGet, + Path: "/query/orderId/:order_id", + Handler: query.QueryDetailByOrderIdHandler(serverCtx), + }, + { + // 查询详情 按订单号 + Method: http.MethodGet, + Path: "/query/orderNo/:order_no", + Handler: query.QueryDetailByOrderNoHandler(serverCtx), + }, + { + // 获取查询临时订单 + Method: http.MethodGet, + Path: "/query/provisional_order/:id", + Handler: query.QueryProvisionalOrderHandler(serverCtx), + }, + { + // 补查 + Method: http.MethodPost, + Path: "/query/recheck/:query_id", + Handler: query.QueryRecheckHandler(serverCtx), + }, + { + // 重试查询 + Method: http.MethodPost, + Path: "/query/retry/:id", + Handler: query.QueryRetryHandler(serverCtx), + }, + { + // 更新查询数据 + Method: http.MethodPost, + Path: "/query/update_data", + Handler: query.UpdateQueryDataHandler(serverCtx), + }, + }..., + ), rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), rest.WithPrefix("/api/v1"), ) @@ -889,30 +928,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { server.AddRoutes( []rest.Route{ - { - // agent mobile code login - Method: http.MethodPost, - Path: "/user/agent_mobile_code_login", - Handler: user.AgentMobileCodeLoginHandler(serverCtx), - }, { // mobile code login Method: http.MethodPost, Path: "/user/mobileCodeLogin", Handler: user.MobileCodeLoginHandler(serverCtx), }, - { - // mobile login - Method: http.MethodPost, - Path: "/user/mobileLogin", - Handler: user.MobileLoginHandler(serverCtx), - }, - { - // register - Method: http.MethodPost, - Path: "/user/register", - Handler: user.RegisterHandler(serverCtx), - }, { // wechat mini auth Method: http.MethodPost, @@ -929,14 +950,23 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { rest.WithPrefix("/api/v1"), ) + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AuthInterceptor}, + []rest.Route{ + { + // 绑定手机号 + Method: http.MethodPost, + Path: "/user/bindMobile", + Handler: user.BindMobileHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1"), + ) + server.AddRoutes( []rest.Route{ - { - // 绑定手机号 - Method: http.MethodPost, - Path: "/user/bindMobile", - Handler: user.BindMobileHandler(serverCtx), - }, { Method: http.MethodPost, Path: "/user/cancelOut", diff --git a/app/main/api/internal/handler/user/agentmobilecodeloginhandler.go b/app/main/api/internal/handler/user/agentmobilecodeloginhandler.go deleted file mode 100644 index c8269eb..0000000 --- a/app/main/api/internal/handler/user/agentmobilecodeloginhandler.go +++ /dev/null @@ -1,30 +0,0 @@ -package user - -import ( - "net/http" - - "qnc-server/app/main/api/internal/logic/user" - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - "qnc-server/common/result" - "qnc-server/pkg/lzkit/validator" - - "github.com/zeromicro/go-zero/rest/httpx" -) - -func AgentMobileCodeLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req types.MobileCodeLoginReq - if err := httpx.Parse(r, &req); err != nil { - result.ParamErrorResult(r, w, err) - return - } - if err := validator.Validate(req); err != nil { - result.ParamValidateErrorResult(r, w, err) - return - } - l := user.NewAgentMobileCodeLoginLogic(r.Context(), svcCtx) - resp, err := l.AgentMobileCodeLogin(&req) - result.HttpResult(r, w, resp, err) - } -} diff --git a/app/main/api/internal/handler/user/mobileloginhandler.go b/app/main/api/internal/handler/user/mobileloginhandler.go deleted file mode 100644 index d02a2d1..0000000 --- a/app/main/api/internal/handler/user/mobileloginhandler.go +++ /dev/null @@ -1,30 +0,0 @@ -package user - -import ( - "net/http" - - "qnc-server/app/main/api/internal/logic/user" - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - "qnc-server/common/result" - "qnc-server/pkg/lzkit/validator" - - "github.com/zeromicro/go-zero/rest/httpx" -) - -func MobileLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req types.MobileLoginReq - if err := httpx.Parse(r, &req); err != nil { - result.ParamErrorResult(r, w, err) - return - } - if err := validator.Validate(req); err != nil { - result.ParamValidateErrorResult(r, w, err) - return - } - l := user.NewMobileLoginLogic(r.Context(), svcCtx) - resp, err := l.MobileLogin(&req) - result.HttpResult(r, w, resp, err) - } -} diff --git a/app/main/api/internal/handler/user/registerhandler.go b/app/main/api/internal/handler/user/registerhandler.go deleted file mode 100644 index 693096b..0000000 --- a/app/main/api/internal/handler/user/registerhandler.go +++ /dev/null @@ -1,30 +0,0 @@ -package user - -import ( - "net/http" - - "qnc-server/app/main/api/internal/logic/user" - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - "qnc-server/common/result" - "qnc-server/pkg/lzkit/validator" - - "github.com/zeromicro/go-zero/rest/httpx" -) - -func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req types.RegisterReq - if err := httpx.Parse(r, &req); err != nil { - result.ParamErrorResult(r, w, err) - return - } - if err := validator.Validate(req); err != nil { - result.ParamValidateErrorResult(r, w, err) - return - } - l := user.NewRegisterLogic(r.Context(), svcCtx) - resp, err := l.Register(&req) - result.HttpResult(r, w, resp, err) - } -} diff --git a/app/main/api/internal/logic/admin_auth/adminloginlogic.go b/app/main/api/internal/logic/admin_auth/adminloginlogic.go index 1409bbe..50a70a4 100644 --- a/app/main/api/internal/logic/admin_auth/adminloginlogic.go +++ b/app/main/api/internal/logic/admin_auth/adminloginlogic.go @@ -5,6 +5,7 @@ import ( "qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/types" + "qnc-server/app/main/model" jwtx "qnc-server/common/jwt" "qnc-server/common/xerr" "qnc-server/pkg/lzkit/crypto" @@ -71,7 +72,14 @@ func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.Admi // 5. 生成token refreshToken := l.svcCtx.Config.JwtAuth.RefreshAfter expiresAt := l.svcCtx.Config.JwtAuth.AccessExpire - token, err := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, expiresAt) + claims := jwtx.JwtClaims{ + UserId: user.Id, + AgentId: 0, + Platform: model.PlatformAdmin, + UserType: model.UserTypeAdmin, + IsAgent: model.AgentStatusNo, + } + token, err := jwtx.GenerateJwtToken(claims, l.svcCtx.Config.JwtAuth.AccessSecret, expiresAt) if err != nil { return nil, errors.Wrapf(xerr.NewErrMsg("生成token失败"), "用户登录, 生成token失败, 用户名: %s", req.Username) } diff --git a/app/main/api/internal/logic/agent/applyforagentlogic.go b/app/main/api/internal/logic/agent/applyforagentlogic.go index 89d7227..9da02a2 100644 --- a/app/main/api/internal/logic/agent/applyforagentlogic.go +++ b/app/main/api/internal/logic/agent/applyforagentlogic.go @@ -5,7 +5,7 @@ import ( "database/sql" "fmt" "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" + "qnc-server/common/ctxdata" "qnc-server/common/xerr" "qnc-server/pkg/lzkit/crypto" "time" @@ -35,6 +35,10 @@ func NewApplyForAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *App } func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *types.AgentApplyResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, %v", err) + } secretKey := l.svcCtx.Config.Encrypt.SecretKey encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) if err != nil { @@ -63,29 +67,20 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err) } if user == nil { - user = &model.User{Mobile: sql.NullString{String: encryptedMobile, Valid: true}} - // if len(main.Nickname) == 0 { - // main.Nickname = encryptedMobile - // } - insertResult, userInsertErr := l.svcCtx.UserModel.Insert(transCtx, session, user) - if userInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 数据库插入新用户失败, mobile%s, err: %+v", encryptedMobile, userInsertErr) + userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err) } - lastId, lastInsertIdErr := insertResult.LastInsertId() - if lastInsertIdErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 获取新用户ID失败, err:%+v, main:%+v", lastInsertIdErr, user) - } - user.Id = lastId - userID = lastId - userAuth := new(model.UserAuth) - userAuth.UserId = lastId - userAuth.AuthKey = encryptedMobile - userAuth.AuthType = model.UserAuthTypeAgentDirect - if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(transCtx, session, userAuth); userAuthInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 数据库插入用户认证失败, err:%+v", userAuthInsertErr) + } else { + if claims != nil && claims.UserType == model.UserTypeTemp { + // 临时用户,转为正式用户 + err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err) + } } + userID = user.Id } - userID = user.Id // 使用SelectBuilder构建查询,查找符合user_id的记录并按创建时间降序排序获取最新一条 builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", user.Id).OrderBy("create_time DESC").Limit(1) @@ -113,17 +108,6 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 保存代理审核信息失败: %v", insetAgentAuditErr) } - //agentAuditID, _ := agentAuditInsert.LastInsertId() - //agentAuditRow, findAgentAuditModelErr := l.svcCtx.AgentAuditModel.FindOne(l.ctx, agentAuditID) - //if findAgentAuditModelErr != nil { - // return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 查找代理审核信息失败: %v", insetAgentAuditErr) - //} - //agentAuditRow.Status = 1 - //updateAgentAuditErr := l.svcCtx.AgentAuditModel.UpdateWithVersion(transCtx, session, agentAuditRow) - //if updateAgentAuditErr != nil { - // return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 通过代理审核失败: %+v", updateAgentAuditErr) - //} - // 新增代理 var agentModel model.Agent agentModel.Mobile = agentAudit.Mobile @@ -169,9 +153,9 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type if transErr != nil { return nil, transErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 生成token失败 : %d", userID) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", userID) } // 获取当前时间戳 diff --git a/app/main/api/internal/logic/agent/getagentinfologic.go b/app/main/api/internal/logic/agent/getagentinfologic.go index 145b32e..689912d 100644 --- a/app/main/api/internal/logic/agent/getagentinfologic.go +++ b/app/main/api/internal/logic/agent/getagentinfologic.go @@ -31,10 +31,18 @@ func NewGetAgentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetA } func (l *GetAgentInfoLogic) GetAgentInfo() (resp *types.AgentInfoResp, err error) { - userID, err := ctxdata.GetUidFromCtx(l.ctx) + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err) } + userID := claims.UserId + userType := claims.UserType + if userType == model.UserTypeTemp { + return &types.AgentInfoResp{ + IsAgent: false, + Status: 3, + }, nil + } agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) if err != nil { if errors.Is(err, sql.ErrNoRows) { diff --git a/app/main/api/internal/logic/agent/getagentpromotionqrcodelogic.go b/app/main/api/internal/logic/agent/getagentpromotionqrcodelogic.go new file mode 100644 index 0000000..a0cf54c --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentpromotionqrcodelogic.go @@ -0,0 +1,72 @@ +package agent + +import ( + "context" + "fmt" + "net/http" + + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentPromotionQrcodeLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext + writer http.ResponseWriter +} + +func NewGetAgentPromotionQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *GetAgentPromotionQrcodeLogic { + return &GetAgentPromotionQrcodeLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + writer: writer, + } +} + +func (l *GetAgentPromotionQrcodeLogic) GetAgentPromotionQrcode(req *types.GetAgentPromotionQrcodeReq) error { + // 1. 参数验证 + if req.QrcodeUrl == "" { + return errors.Wrapf(xerr.NewErrMsg("二维码URL不能为空"), "二维码URL为空") + } + + if req.QrcodeType == "" { + req.QrcodeType = "promote" // 设置默认类型 + } + + // 3. 检查指定类型的背景图是否存在 + if !l.svcCtx.ImageService.CheckImageExists(req.QrcodeType) { + l.Errorf("指定的二维码类型对应的背景图不存在: %s", req.QrcodeType) + return errors.Wrapf(xerr.NewErrMsg("指定的二维码类型不支持"), "二维码类型: %s", req.QrcodeType) + } + + // 4. 处理图片,添加二维码 + imageData, contentType, err := l.svcCtx.ImageService.ProcessImageWithQRCode(req.QrcodeType, req.QrcodeUrl) + if err != nil { + l.Errorf("处理图片失败: %v, 类型: %s, URL: %s", err, req.QrcodeType, req.QrcodeUrl) + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广二维码图片失败: %v", err) + } + + // 5. 设置响应头 + l.writer.Header().Set("Content-Type", contentType) + l.writer.Header().Set("Content-Length", fmt.Sprintf("%d", len(imageData))) + l.writer.Header().Set("Cache-Control", "public, max-age=3600") // 缓存1小时 + l.writer.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"qrcode_%s.png\"", req.QrcodeType)) + + // 6. 写入图片数据 + _, err = l.writer.Write(imageData) + if err != nil { + l.Errorf("写入图片数据失败: %v", err) + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "输出图片数据失败: %v", err) + } + + l.Infof("成功生成代理推广二维码图片,类型: %s, URL: %s, 图片大小: %d bytes", + req.QrcodeType, req.QrcodeUrl, len(imageData)) + + return nil +} diff --git a/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go b/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go index 5be0a30..80b762f 100644 --- a/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go +++ b/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go @@ -48,7 +48,7 @@ func (l *SaveAgentMembershipUserConfigLogic) SaveAgentMembershipUserConfig(req * agentMembershipUserConfigModel = &model.AgentMembershipUserConfig{ UserId: userID, AgentId: agentModel.Id, - ProductId: req.ProductID, + ProductId: req.ProductID, PriceRatio: req.PriceRatio, PriceIncreaseAmount: req.PriceIncreaseAmount, PriceRangeFrom: req.PriceRangeFrom, diff --git a/app/main/api/internal/logic/query/queryservicelogic.go b/app/main/api/internal/logic/query/queryservicelogic.go index 8f8b166..953f316 100644 --- a/app/main/api/internal/logic/query/queryservicelogic.go +++ b/app/main/api/internal/logic/query/queryservicelogic.go @@ -7,7 +7,6 @@ import ( "fmt" "qnc-server/app/main/api/internal/service" "qnc-server/common/ctxdata" - jwtx "qnc-server/common/jwt" "qnc-server/common/xerr" "qnc-server/pkg/lzkit/crypto" "qnc-server/pkg/lzkit/validator" @@ -99,8 +98,8 @@ func (l *QueryServiceLogic) ProcessMarriageLogic(req *types.QueryServiceReq) (*t if cacheDataErr != nil { return nil, cacheDataErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -153,9 +152,8 @@ func (l *QueryServiceLogic) ProcessHomeServiceLogic(req *types.QueryServiceReq) if cacheDataErr != nil { return nil, cacheDataErr } - - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -209,8 +207,8 @@ func (l *QueryServiceLogic) ProcessRiskAssessmentLogic(req *types.QueryServiceRe return nil, cacheDataErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -263,8 +261,8 @@ func (l *QueryServiceLogic) ProcessCompanyInfoLogic(req *types.QueryServiceReq) return nil, cacheDataErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -319,8 +317,8 @@ func (l *QueryServiceLogic) ProcessRentalInfoLogic(req *types.QueryServiceReq) ( return nil, cacheDataErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -375,8 +373,8 @@ func (l *QueryServiceLogic) ProcessPreLoanBackgroundCheckLogic(req *types.QueryS return nil, cacheDataErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -428,8 +426,8 @@ func (l *QueryServiceLogic) ProcessBackgroundCheckLogic(req *types.QueryServiceR return nil, cacheDataErr } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) } @@ -579,36 +577,34 @@ func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product str return outTradeNo, nil } -// GetOrCreateUser 获取或创建用户 -// 1. 如果上下文中已有用户ID,直接返回 -// 2. 如果是代理查询或APP请求,创建新用户 -// 3. 其他情况返回未登录错误 +// GetOrCreateUser 获取或创建用户(过期) func (l *QueryServiceLogic) GetOrCreateUser() (int64, error) { // 尝试获取用户ID - userID, err := ctxdata.GetUidFromCtx(l.ctx) - if err == nil { - return userID, nil // 已有用户ID,直接返回 - } - - // 如果不是未登录错误,说明是其他错误,直接返回 - if !ctxdata.IsNoUserIdError(err) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil { return 0, err } + userID := claims.UserId + return userID, nil + // // 如果不是未登录错误,说明是其他错误,直接返回 + // if !ctxdata.IsNoUserIdError(err) { + // return 0, err + // } - // 检查是否是代理查询或APP请求 - isAgentQuery := false - if agentID, ok := l.ctx.Value("agentIdentifier").(string); ok && agentID != "" { - isAgentQuery = true - } - if app, ok := l.ctx.Value("app").(bool); ok && app { - isAgentQuery = true - } + // // 检查是否是代理查询或APP请求 + // isAgentQuery := false + // if agentID, ok := l.ctx.Value("agentIdentifier").(string); ok && agentID != "" { + // isAgentQuery = true + // } + // if app, ok := l.ctx.Value("app").(bool); ok && app { + // isAgentQuery = true + // } - // 如果不是代理查询或APP请求,返回未登录错误 - if !isAgentQuery { - return 0, ctxdata.ErrNoUserIdInCtx - } + // // 如果不是代理查询或APP请求,返回未登录错误 + // if !isAgentQuery { + // return 0, ctxdata.ErrNoInCtx + // } - // 创建新用户 - return l.svcCtx.UserService.RegisterUUIDUser(l.ctx) + // // 创建新用户 + // return l.svcCtx.UserService.RegisterUUIDUser(l.ctx) } diff --git a/app/main/api/internal/logic/user/agentmobilecodeloginlogic.go b/app/main/api/internal/logic/user/agentmobilecodeloginlogic.go deleted file mode 100644 index d616eeb..0000000 --- a/app/main/api/internal/logic/user/agentmobilecodeloginlogic.go +++ /dev/null @@ -1,99 +0,0 @@ -package user - -import ( - "context" - "database/sql" - "fmt" - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" - "qnc-server/common/xerr" - "qnc-server/pkg/lzkit/crypto" - "time" - - "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/stores/redis" - "github.com/zeromicro/go-zero/core/stores/sqlx" - - "github.com/zeromicro/go-zero/core/logx" -) - -type AgentMobileCodeLoginLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewAgentMobileCodeLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentMobileCodeLoginLogic { - return &AgentMobileCodeLoginLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *AgentMobileCodeLoginLogic) AgentMobileCodeLogin(req *types.MobileCodeLoginReq) (resp *types.MobileCodeLoginResp, err error) { - secretKey := l.svcCtx.Config.Encrypt.SecretKey - encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err) - } - // 检查手机号是否在一分钟内已发送过验证码 - redisKey := fmt.Sprintf("%s:%s", "query", encryptedMobile) - cacheCode, err := l.svcCtx.Redis.Get(redisKey) - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期") - } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, err: %+v", err) - } - if cacheCode != req.Code { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确") - } - - user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) - if findUserErr != nil && findUserErr != model.ErrNotFound { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err) - } - if user == nil { - user = &model.User{Mobile: sql.NullString{String: encryptedMobile, Valid: true}} - // if len(main.Nickname) == 0 { - // main.Nickname = "" - // } - if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { - insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user) - if userInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入新用户失败, mobile%s, err: %+v", encryptedMobile, err) - } - lastId, lastInsertIdErr := insertResult.LastInsertId() - if lastInsertIdErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 获取新用户ID失败, err:%+v, main:%+v", lastInsertIdErr, user) - } - user.Id = lastId - - userAuth := new(model.UserAuth) - userAuth.UserId = lastId - userAuth.AuthKey = encryptedMobile - userAuth.AuthType = model.UserAuthTypeH5Mobile - if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); userAuthInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入用户认证失败, err:%+v", userAuthInsertErr) - } - return nil - }); transErr != nil { - return nil, transErr - } - } - token, generaErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", user.Id) - } - - // 获取当前时间戳 - now := time.Now().Unix() - return &types.MobileCodeLoginResp{ - AccessToken: token, - AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, - RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, - }, nil -} diff --git a/app/main/api/internal/logic/user/bindmobilelogic.go b/app/main/api/internal/logic/user/bindmobilelogic.go index 5a22d1d..adad4ff 100644 --- a/app/main/api/internal/logic/user/bindmobilelogic.go +++ b/app/main/api/internal/logic/user/bindmobilelogic.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "time" "qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/types" @@ -15,7 +16,6 @@ import ( "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/redis" - "github.com/zeromicro/go-zero/core/stores/sqlx" ) type BindMobileLogic struct { @@ -33,22 +33,15 @@ func NewBindMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindMo } func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.BindMobileResp, err error) { - userID, getUserIdErr := ctxdata.GetUidFromCtx(l.ctx) - if getUserIdErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, %v", getUserIdErr) + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, %v", err) } secretKey := l.svcCtx.Config.Encrypt.SecretKey encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err) } - user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) - if err != nil && !errors.Is(err, model.ErrNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) - } - if user != nil { - return nil, errors.Wrapf(xerr.NewErrMsg("该手机号已绑定"), "绑定手机号, %v", err) - } // 检查手机号是否在一分钟内已发送过验证码 redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile) cacheCode, err := l.svcCtx.Redis.Get(redisKey) @@ -61,44 +54,38 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind if cacheCode != req.Code { return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile) } - - userModel, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) - } - if userModel.Mobile.Valid && userModel.Mobile.String != "" { - return nil, errors.Wrapf(xerr.NewErrMsg("账号已绑定手机号,无法再次绑定"), "绑定手机号, %v", err) - } - userAuthModel, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, userID, model.UserAuthTypeH5Mobile) + var userID int64 + user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) if err != nil && !errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) } - if userAuthModel != nil { - return nil, errors.Wrapf(xerr.NewErrMsg("账号已绑定手机号,无法再次绑定"), "绑定手机号, %v", err) + if user != nil { + // 进行平台绑定 + if claims != nil { + if claims.UserType == model.UserTypeTemp { + err = l.svcCtx.UserService.TempUserBindUser(l.ctx, nil, user.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 临时用户绑定用户失败: %+v", err) + } + } + } + userID = user.Id + } else { + // 创建账号,并绑定手机号 + userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 注册用户失败: %+v", err) + } } - var userAuth model.UserAuth - userAuth.UserId = userID - userAuth.AuthType = model.UserAuthTypeH5Mobile - userAuth.AuthKey = encryptedMobile - transErr := l.svcCtx.UserAuthModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { - _, err = l.svcCtx.UserAuthModel.Insert(ctx, session, &userAuth) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) - } - userModel.Mobile = sql.NullString{ - String: encryptedMobile, - Valid: true, - } - _, err = l.svcCtx.UserModel.Update(l.ctx, session, userModel) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) - } - return nil - }) - if transErr != nil { - return nil, transErr + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 生成token失败: %+v", err) } - - return &types.BindMobileResp{}, nil + now := time.Now().Unix() + return &types.BindMobileResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil } diff --git a/app/main/api/internal/logic/user/detaillogic.go b/app/main/api/internal/logic/user/detaillogic.go index 738dde1..9341d2b 100644 --- a/app/main/api/internal/logic/user/detaillogic.go +++ b/app/main/api/internal/logic/user/detaillogic.go @@ -30,10 +30,23 @@ func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogi } func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) { - userID, err := ctxdata.GetUidFromCtx(l.ctx) + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) } + + userID := claims.UserId + userType := claims.UserType + if userType == model.UserTypeTemp { + return &types.UserInfoResp{ + UserInfo: types.User{ + Id: userID, + UserType: userType, + Mobile: "", + NickName: "", + }, + }, nil + } user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) if err != nil { if errors.Is(err, model.ErrNotFound) { @@ -46,12 +59,15 @@ func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) { if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %v", err) } + if user.Mobile.Valid { userInfo.Mobile, err = crypto.DecryptMobile(user.Mobile.String, l.svcCtx.Config.Encrypt.SecretKey) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 解密手机号失败, %v", err) } } + userInfo.UserType = claims.UserType + return &types.UserInfoResp{ UserInfo: userInfo, }, nil diff --git a/app/main/api/internal/logic/user/gettokenlogic.go b/app/main/api/internal/logic/user/gettokenlogic.go index 1515011..987ec74 100644 --- a/app/main/api/internal/logic/user/gettokenlogic.go +++ b/app/main/api/internal/logic/user/gettokenlogic.go @@ -3,7 +3,6 @@ package user import ( "context" "qnc-server/common/ctxdata" - jwtx "qnc-server/common/jwt" "qnc-server/common/xerr" "time" @@ -32,11 +31,11 @@ func NewGetTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetToken func (l *GetTokenLogic) GetToken() (resp *types.MobileCodeLoginResp, err error) { userID, err := ctxdata.GetUidFromCtx(l.ctx) if err != nil { - return nil, errors.Wrapf(xerr.NewErrMsg(""), "用户信息, %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) } - token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新token, 生成token失败 : %d", userID) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) } // 获取当前时间戳 now := time.Now().Unix() diff --git a/app/main/api/internal/logic/user/mobilecodeloginlogic.go b/app/main/api/internal/logic/user/mobilecodeloginlogic.go index 824e873..d06fa2d 100644 --- a/app/main/api/internal/logic/user/mobilecodeloginlogic.go +++ b/app/main/api/internal/logic/user/mobilecodeloginlogic.go @@ -7,16 +7,14 @@ import ( "qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/types" "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" "qnc-server/common/xerr" "qnc-server/pkg/lzkit/crypto" "time" "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/stores/redis" - "github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/redis" ) type MobileCodeLoginLogic struct { @@ -53,42 +51,20 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile) } } - + var userID int64 user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) if findUserErr != nil && findUserErr != model.ErrNotFound { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err) } if user == nil { - user = &model.User{Mobile: sql.NullString{String: encryptedMobile, Valid: true}} - // if len(main.Nickname) == 0 { - // main.Nickname = encryptedMobile - // } - if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { - insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user) - if userInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入新用户失败, mobile%s, err: %+v", encryptedMobile, err) - } - lastId, lastInsertIdErr := insertResult.LastInsertId() - if lastInsertIdErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 获取新用户ID失败, err:%+v, main:%+v", lastInsertIdErr, user) - } - user.Id = lastId - - userAuth := new(model.UserAuth) - userAuth.UserId = lastId - userAuth.AuthKey = encryptedMobile - userAuth.AuthType = model.UserAuthTypeAppMobile - if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); userAuthInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入用户认证失败, err:%+v", userAuthInsertErr) - } - return nil - }); transErr != nil { - return nil, transErr + userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 注册用户失败: %+v", err) } } - token, generaErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", user.Id) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", userID) } // 获取当前时间戳 diff --git a/app/main/api/internal/logic/user/mobileloginlogic.go b/app/main/api/internal/logic/user/mobileloginlogic.go deleted file mode 100644 index 45e2089..0000000 --- a/app/main/api/internal/logic/user/mobileloginlogic.go +++ /dev/null @@ -1,65 +0,0 @@ -package user - -import ( - "context" - "database/sql" - "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" - "qnc-server/common/tool" - "qnc-server/common/xerr" - "qnc-server/pkg/lzkit/crypto" - "qnc-server/pkg/lzkit/lzUtils" - "time" - - "github.com/pkg/errors" - - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - - "github.com/zeromicro/go-zero/core/logx" -) - -type MobileLoginLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewMobileLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MobileLoginLogic { - return &MobileLoginLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *MobileLoginLogic) MobileLogin(req *types.MobileLoginReq) (resp *types.MobileCodeLoginResp, err error) { - secretKey := l.svcCtx.Config.Encrypt.SecretKey - encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err) - } - user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) - if findUserErr != nil && findUserErr != model.ErrNotFound { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile%s, err: %+v", encryptedMobile, err) - } - if user == nil { - return nil, errors.Wrapf(xerr.NewErrMsg("手机号码未注册"), "手机登录, 手机号未注册:%s", encryptedMobile) - } - if !(tool.Md5ByString(req.Password) == lzUtils.NullStringToString(user.Password)) { - return nil, errors.Wrapf(xerr.NewErrMsg("密码不正确"), "手机登录, 密码匹配不正确%s", encryptedMobile) - } - - token, generaErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", user.Id) - } - - // 获取当前时间戳 - now := time.Now().Unix() - return &types.MobileCodeLoginResp{ - AccessToken: token, - AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, - RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, - }, nil -} diff --git a/app/main/api/internal/logic/user/registerlogic.go b/app/main/api/internal/logic/user/registerlogic.go deleted file mode 100644 index 3e1e4bf..0000000 --- a/app/main/api/internal/logic/user/registerlogic.go +++ /dev/null @@ -1,106 +0,0 @@ -package user - -import ( - "context" - "database/sql" - "fmt" - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" - "qnc-server/common/tool" - "qnc-server/common/xerr" - "qnc-server/pkg/lzkit/crypto" - "qnc-server/pkg/lzkit/lzUtils" - "time" - - "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/stores/redis" - "github.com/zeromicro/go-zero/core/stores/sqlx" - - "github.com/zeromicro/go-zero/core/logx" -) - -type RegisterLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic { - return &RegisterLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) { - secretKey := l.svcCtx.Config.Encrypt.SecretKey - encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机注册, 加密手机号失败: %+v", err) - } - // 检查手机号是否在一分钟内已发送过验证码 - redisKey := fmt.Sprintf("%s:%s", "register", encryptedMobile) - cacheCode, err := l.svcCtx.Redis.Get(redisKey) - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机注册, 验证码过期: %s", encryptedMobile) - } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) - } - if cacheCode != req.Code { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机注册, 验证码不正确: %s", encryptedMobile) - } - hasUser, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) - if findUserErr != nil && findUserErr != model.ErrNotFound { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 读取数据库获取用户失败, mobile%s, err: %+v", encryptedMobile, err) - } - if hasUser != nil { - return nil, errors.Wrapf(xerr.NewErrMsg("该手机号码已注册"), "手机注册, 手机号码已注册, mobile:%s", encryptedMobile) - } - var userId int64 - if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { - user := new(model.User) - user.Mobile = sql.NullString{String: encryptedMobile, Valid: true} - // if len(main.Nickname) == 0 { - // main.Nickname = encryptedMobile - // } - if len(req.Password) > 0 { - user.Password = lzUtils.StringToNullString(tool.Md5ByString(req.Password)) - } - insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user) - if userInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入新用户失败, mobile%s, err: %+v", encryptedMobile, err) - } - lastId, lastInsertIdErr := insertResult.LastInsertId() - if lastInsertIdErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 获取新用户ID失败, err:%+v, main:%+v", lastInsertIdErr, user) - } - userId = lastId - - userAuth := new(model.UserAuth) - userAuth.UserId = lastId - userAuth.AuthKey = encryptedMobile - userAuth.AuthType = model.UserAuthTypeAppMobile - if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); userAuthInsertErr != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入用户认证失败, err:%+v", userAuthInsertErr) - } - return nil - }); transErr != nil { - return nil, transErr - } - - token, generaErr := jwtx.GenerateJwtToken(userId, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if generaErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机注册, 生成jwt token失败, userid: %d, err:%+v", userId, generaErr) - } - // 获取当前时间戳 - now := time.Now().Unix() - return &types.RegisterResp{ - AccessToken: token, - AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, - RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, - }, nil -} diff --git a/app/main/api/internal/logic/user/wxh5authlogic.go b/app/main/api/internal/logic/user/wxh5authlogic.go index 1ab6d5f..07d1f0d 100644 --- a/app/main/api/internal/logic/user/wxh5authlogic.go +++ b/app/main/api/internal/logic/user/wxh5authlogic.go @@ -7,12 +7,10 @@ import ( "io" "net/http" "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" "qnc-server/common/xerr" "time" "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/stores/sqlx" "qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/types" @@ -40,60 +38,48 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err) } - if accessTokenResp.AccessToken == "" || accessTokenResp.Openid == "" { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token为空: %v", accessTokenResp) - } + // Step 2: 查找用户授权信息 - userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5, accessTokenResp.Openid) + userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid) if findErr != nil && !errors.Is(findErr, model.ErrNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询用户授权失败,findErr: %v", findErr) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", findErr) } - // Step 3: 查找或创建用户 + // Step 3: 处理用户信息 var user *model.User if userAuth != nil { - // 授权信息存在,查找用户 - userModel, findUserErr := l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId) - if findUserErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询用户失败,userId: %v", findUserErr) + // 已存在用户,直接登录 + user, err = l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户信息失败: %v", err) } - user = userModel } else { - // 授权信息不存在,创建新用户 - user = &model.User{} - if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(context context.Context, session sqlx.Session) error { - // 插入数据库 - insertResult, insertErr := l.svcCtx.UserModel.Insert(l.ctx, session, user) - if insertErr != nil { - return errors.Wrapf(insertErr, "创建新用户失败,openid: %s", accessTokenResp.Openid) - } - // 获取插入后生成的 main.Id - lastInsertId, lastInsertIdErr := insertResult.LastInsertId() - if lastInsertIdErr != nil { - return errors.Wrapf(lastInsertIdErr, "获取新用户ID失败,openid: %s", accessTokenResp.Openid) - } - user.Id = lastInsertId - // 创建用户授权信息 - userAuth = &model.UserAuth{ - UserId: user.Id, + // 检查临时用户表 + userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err) + } + + if userTemp == nil { + // 创建临时用户记录 + userTemp = &model.UserTemp{ + AuthType: model.UserAuthTypeWxh5OpenID, AuthKey: accessTokenResp.Openid, - AuthType: model.UserAuthTypeWxh5, // 微信小程序 } - if _, insertUserAuthErr := l.svcCtx.UserAuthModel.Insert(l.ctx, session, userAuth); insertUserAuthErr != nil { - return errors.Wrapf(insertUserAuthErr, "创建用户授权失败,openid: %s", accessTokenResp.Openid) + _, err = l.svcCtx.UserTempModel.Insert(l.ctx, nil, userTemp) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建临时用户信息失败: %v", err) } - return nil - }); transErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建新用户事务失败: %v", transErr) } } // Step 4: 生成JWT Token - token, genErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if genErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成JWT token失败: %v", genErr) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, user.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err) } + // Step 5: 返回登录结果 now := time.Now().Unix() return &types.WXH5AuthResp{ AccessToken: token, @@ -130,9 +116,8 @@ func (l *WxH5AuthLogic) GetAccessToken(code string) (*AccessTokenResp, error) { return nil, err } - //if accessTokenResp.AccessToken == "" { - // return nil, errors.New("accessTokenResp.AccessToken为空") - //} - + if accessTokenResp.AccessToken == "" || accessTokenResp.Openid == "" { + return nil, errors.New("accessTokenResp.AccessToken为空") + } return &accessTokenResp, nil } diff --git a/app/main/api/internal/logic/user/wxminiauthlogic copy.go b/app/main/api/internal/logic/user/wxminiauthlogic copy.go deleted file mode 100644 index e95b035..0000000 --- a/app/main/api/internal/logic/user/wxminiauthlogic copy.go +++ /dev/null @@ -1,167 +0,0 @@ -package user - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "qnc-server/app/main/model" - jwtx "qnc-server/common/jwt" - "qnc-server/common/xerr" - "time" - - "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/stores/sqlx" - - "qnc-server/app/main/api/internal/svc" - "qnc-server/app/main/api/internal/types" - - "github.com/zeromicro/go-zero/core/logx" -) - -type WxMiniAuthLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMiniAuthLogic { - return &WxMiniAuthLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) { - // 1. 获取session_key和openid - sessionKeyResp, err := l.GetSessionKey(req.Code) - if err != nil { - return nil, err - } - - // 2. 查找用户授权信息 - userAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid) - if err != nil && !errors.Is(err, model.ErrNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", err) - } - - // 3. 处理用户信息 - var user *model.User - if userAuth != nil { - // 已存在用户,直接登录 - user, err = l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户信息失败: %v", err) - } - } else { - userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid) - if err != nil && !errors.Is(err, model.ErrNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err) - } - if userTemp == nil { - userTemp.AuthType = model.UserAuthTypeWxMiniOpenID - userTemp.AuthKey = sessionKeyResp.Openid - user, err = l.svcCtx.UserModel.Insert(l.ctx, userTemp) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户信息失败: %v", err) - } - userAuth = &model.UserAuth{ - UserId: user.Id, - AuthKey: sessionKeyResp.Openid, - AuthType: model.UserAuthTypeWxMiniOpenID, - } - // 新用户,创建用户记录 - user = &model.User{} - if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { - // 插入用户基本信息 - insertResult, err := l.svcCtx.UserModel.Insert(ctx, session, user) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建新用户失败: %v", err) - } - lastId, err := insertResult.LastInsertId() - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新用户ID失败: %v", err) - } - user.Id = lastId - - // 创建用户授权信息 - userAuth = &model.UserAuth{ - UserId: user.Id, - AuthKey: sessionKeyResp.Openid, - AuthType: model.UserAuthTypeWxMiniOpenID, - } - if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户授权失败: %v", err) - } - return nil - }); transErr != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建新用户事务失败: %v", transErr) - } - } - - // 4. 生成JWT Token - token, err := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err) - } - - // 5. 返回登录结果 - now := time.Now().Unix() - return &types.WXMiniAuthResp{ - AccessToken: token, - AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, - RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, - }, nil -} - -// SessionKeyResp 小程序登录返回结构 -type SessionKeyResp struct { - Openid string `json:"openid"` - SessionKey string `json:"session_key"` - Unionid string `json:"unionid,omitempty"` - ErrCode int `json:"errcode,omitempty"` - ErrMsg string `json:"errmsg,omitempty"` -} - -// GetSessionKey 通过code获取小程序的session_key和openid -func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) { - appID := l.svcCtx.Config.WechatMini.AppID - appSecret := l.svcCtx.Config.WechatMini.AppSecret - - url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", - appID, appSecret, code) - - resp, err := http.Get(url) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取响应失败: %v", err) - } - - var sessionKeyResp SessionKeyResp - if err = json.Unmarshal(body, &sessionKeyResp); err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析响应失败: %v", err) - } - - // 检查微信返回的错误码 - if sessionKeyResp.ErrCode != 0 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), - "微信接口返回错误: errcode=%d, errmsg=%s", - sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg) - } - - // 验证必要字段 - if sessionKeyResp.Openid == "" || sessionKeyResp.SessionKey == "" { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), - "微信接口返回数据不完整: openid=%s, session_key=%s", - sessionKeyResp.Openid, sessionKeyResp.SessionKey) - } - - return &sessionKeyResp, nil -} diff --git a/app/main/api/internal/logic/user/wxminiauthlogic.go b/app/main/api/internal/logic/user/wxminiauthlogic.go index 5e94021..5933f2b 100644 --- a/app/main/api/internal/logic/user/wxminiauthlogic.go +++ b/app/main/api/internal/logic/user/wxminiauthlogic.go @@ -2,6 +2,15 @@ package user import ( "context" + "encoding/json" + "fmt" + "io" + "net/http" + "qnc-server/app/main/model" + "qnc-server/common/xerr" + "time" + + "github.com/pkg/errors" "qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/types" @@ -24,7 +33,114 @@ func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMini } func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) { - // todo: add your logic here and delete this line + // 1. 获取session_key和openid + sessionKeyResp, err := l.GetSessionKey(req.Code) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err) + } - return + // 2. 查找用户授权信息 + userAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", err) + } + + // 3. 处理用户信息 + var userID int64 + if userAuth != nil { + // 已存在用户,直接登录 + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户信息失败: %v", err) + } + userID = user.Id + } else { + // 注册临时用户 + userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err) + } + if userTemp == nil { + // 创建新的临时用户 + userTemp = &model.UserTemp{} + userTemp.AuthType = model.UserAuthTypeWxMiniOpenID + userTemp.AuthKey = sessionKeyResp.Openid + result, err := l.svcCtx.UserTempModel.Insert(l.ctx, nil, userTemp) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建临时用户信息失败: %v", err) + } + // 获取新创建的临时用户ID + userID, err = result.LastInsertId() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的临时用户ID失败: %v", err) + } + } else { + // 使用已存在的临时用户ID + userID = userTemp.Id + } + } + + // 4. 生成JWT Token + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err) + } + + // 5. 返回登录结果 + now := time.Now().Unix() + return &types.WXMiniAuthResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// SessionKeyResp 小程序登录返回结构 +type SessionKeyResp struct { + Openid string `json:"openid"` + SessionKey string `json:"session_key"` + Unionid string `json:"unionid,omitempty"` + ErrCode int `json:"errcode,omitempty"` + ErrMsg string `json:"errmsg,omitempty"` +} + +// GetSessionKey 通过code获取小程序的session_key和openid +func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) { + appID := l.svcCtx.Config.WechatMini.AppID + appSecret := l.svcCtx.Config.WechatMini.AppSecret + + url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", + appID, appSecret, code) + + resp, err := http.Get(url) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取响应失败: %v", err) + } + + var sessionKeyResp SessionKeyResp + if err = json.Unmarshal(body, &sessionKeyResp); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析响应失败: %v", err) + } + + // 检查微信返回的错误码 + if sessionKeyResp.ErrCode != 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "微信接口返回错误: errcode=%d, errmsg=%s", + sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg) + } + + // 验证必要字段 + if sessionKeyResp.Openid == "" || sessionKeyResp.SessionKey == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "微信接口返回数据不完整: openid=%s, session_key=%s", + sessionKeyResp.Openid, sessionKeyResp.SessionKey) + } + + return &sessionKeyResp, nil } diff --git a/app/main/api/internal/middleware/authinterceptormiddleware.go b/app/main/api/internal/middleware/authinterceptormiddleware.go index 86fdc01..fbcec70 100644 --- a/app/main/api/internal/middleware/authinterceptormiddleware.go +++ b/app/main/api/internal/middleware/authinterceptormiddleware.go @@ -2,11 +2,8 @@ package middleware import ( "context" - "encoding/json" - "fmt" "net/http" "qnc-server/app/main/api/internal/config" - "qnc-server/common/ctxdata" jwtx "qnc-server/common/jwt" "qnc-server/common/xerr" @@ -41,17 +38,14 @@ func (m *AuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFu } // 解析JWT令牌 - userId, err := jwtx.ParseJwtToken(authHeader, m.Config.JwtAuth.AccessSecret) + claims, err := jwtx.ParseJwtToken(authHeader, m.Config.JwtAuth.AccessSecret) if err != nil { // JWT解析失败,返回401错误 httpx.Error(w, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err)) return } - // 将用户ID转换为json.Number类型后添加到请求上下文 - userIdStr := fmt.Sprintf("%d", userId) - userIdJsonNum := json.Number(userIdStr) - ctx := context.WithValue(r.Context(), ctxdata.CtxKeyJwtUserId, userIdJsonNum) + ctx := context.WithValue(r.Context(), jwtx.ExtraKey, claims) // 使用新的上下文继续处理请求 next(w, r.WithContext(ctx)) diff --git a/app/main/api/internal/middleware/sourceinterceptormiddleware.go b/app/main/api/internal/middleware/global_sourceinterceptor_middleware.go similarity index 53% rename from app/main/api/internal/middleware/sourceinterceptormiddleware.go rename to app/main/api/internal/middleware/global_sourceinterceptor_middleware.go index 747f848..c7197b3 100644 --- a/app/main/api/internal/middleware/sourceinterceptormiddleware.go +++ b/app/main/api/internal/middleware/global_sourceinterceptor_middleware.go @@ -6,28 +6,16 @@ import ( ) const ( - BrandKey = "X-Brand" PlatformKey = "X-Platform" ) -type SourceInterceptorMiddleware struct { -} - -func NewSourceInterceptorMiddleware() *SourceInterceptorMiddleware { - return &SourceInterceptorMiddleware{} -} - -func (m *SourceInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { +func GlobalSourceInterceptor(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // 获取请求头 X-Brand 和 X-Platform 的值 - brand := r.Header.Get(BrandKey) + // 获取请求头 X-Platform 的值 platform := r.Header.Get(PlatformKey) // 将值放入新的 context 中 ctx := r.Context() - if brand != "" { - ctx = context.WithValue(ctx, "brand", brand) - } if platform != "" { ctx = context.WithValue(ctx, "platform", platform) } diff --git a/app/main/api/internal/middleware/userauthinterceptormiddleware.go b/app/main/api/internal/middleware/userauthinterceptormiddleware.go new file mode 100644 index 0000000..54ea266 --- /dev/null +++ b/app/main/api/internal/middleware/userauthinterceptormiddleware.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "fmt" + "net/http" + "qnc-server/app/main/model" + "qnc-server/common/ctxdata" + "qnc-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/rest/httpx" +) + +type UserAuthInterceptorMiddleware struct { +} + +func NewUserAuthInterceptorMiddleware() *UserAuthInterceptorMiddleware { + return &UserAuthInterceptorMiddleware{} +} + +func (m *UserAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + claims, err := ctxdata.GetClaimsFromCtx(r.Context()) + if err != nil { + httpx.Error(w, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err)) + return + } + fmt.Println(claims) + if claims.UserType == model.UserTypeTemp { + httpx.Error(w, errors.Wrapf(xerr.NewErrCode(xerr.USER_NEED_BIND_MOBILE), "token解析失败: %v", err)) + return + } + next(w, r) + } +} diff --git a/app/main/api/internal/queue/cleanQueryData.go b/app/main/api/internal/queue/cleanQueryData.go index 152b5f4..423e41d 100644 --- a/app/main/api/internal/queue/cleanQueryData.go +++ b/app/main/api/internal/queue/cleanQueryData.go @@ -37,5 +37,5 @@ func (l *CleanQueryDataHandler) ProcessTask(ctx context.Context, t *asynq.Task) } logx.Infof("%s - 查询数据清理完成,共删除 %d 条记录", time.Now().Format("2006-01-02 15:04:05"), result) - return nil + return nil } diff --git a/app/main/api/internal/service/alipayService.go b/app/main/api/internal/service/alipayService.go index 2fe2ce3..aabfe01 100644 --- a/app/main/api/internal/service/alipayService.go +++ b/app/main/api/internal/service/alipayService.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "qnc-server/app/main/api/internal/config" + "qnc-server/app/main/model" "qnc-server/pkg/lzkit/lzUtils" "strconv" "sync/atomic" @@ -101,13 +102,13 @@ func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, s // 根据 ctx 中的 platform 判断平台 platform, platformOk := ctx.Value("platform").(string) if !platformOk { - return "", fmt.Errorf("无的支付平台: %s", platform) + return "", fmt.Errorf("支付平台不存在: %s", platform) } switch platform { - case "app": + case model.PlatformApp: // 调用App支付的创建方法 return a.CreateAlipayAppOrder(amount, subject, outTradeNo) - case "h5": + case model.PlatformH5: // 调用H5支付的创建方法,并传入 returnUrl return a.CreateAlipayH5Order(amount, subject, outTradeNo) default: diff --git a/app/main/api/internal/service/imageService.go b/app/main/api/internal/service/imageService.go new file mode 100644 index 0000000..c827da2 --- /dev/null +++ b/app/main/api/internal/service/imageService.go @@ -0,0 +1,173 @@ +package service + +import ( + "bytes" + "fmt" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" + + "github.com/fogleman/gg" + "github.com/skip2/go-qrcode" + "github.com/zeromicro/go-zero/core/logx" +) + +type ImageService struct { + baseImagePath string +} + +func NewImageService() *ImageService { + return &ImageService{ + baseImagePath: "static/images", // 原图存放目录 + } +} + +// ProcessImageWithQRCode 处理图片,在中间添加二维码 +func (s *ImageService) ProcessImageWithQRCode(qrcodeType, qrcodeUrl string) ([]byte, string, error) { + // 1. 根据qrcodeType确定使用哪张背景图 + var backgroundImageName string + switch qrcodeType { + case "promote": + backgroundImageName = "tg_qrcode_1.jpg" + case "invitation": + backgroundImageName = "yq_qrcode_1.png" + default: + backgroundImageName = "tg_qrcode_1.jpg" // 默认使用第一张图片 + } + + // 2. 读取原图 + originalImagePath := filepath.Join(s.baseImagePath, backgroundImageName) + originalImage, err := s.loadImage(originalImagePath) + if err != nil { + logx.Errorf("加载原图失败: %v, 图片路径: %s", err, originalImagePath) + return nil, "", fmt.Errorf("加载原图失败: %v", err) + } + + // 3. 获取原图尺寸 + bounds := originalImage.Bounds() + imgWidth := bounds.Dx() + imgHeight := bounds.Dy() + + // 4. 创建绘图上下文 + dc := gg.NewContext(imgWidth, imgHeight) + + // 5. 绘制原图作为背景 + dc.DrawImageAnchored(originalImage, imgWidth/2, imgHeight/2, 0.5, 0.5) + + // 6. 生成二维码(去掉白边) + qrCode, err := qrcode.New(qrcodeUrl, qrcode.Medium) + if err != nil { + logx.Errorf("生成二维码失败: %v, 二维码内容: %s", err, qrcodeUrl) + return nil, "", fmt.Errorf("生成二维码失败: %v", err) + } + // 禁用二维码边框,去掉白边 + qrCode.DisableBorder = true + + // 7. 根据二维码类型设置不同的尺寸和位置 + var qrSize int + var qrX, qrY int + + switch qrcodeType { + case "promote": + // promote类型:精确设置二维码尺寸 + qrSize = 280 // 固定尺寸280px + // 左下角位置:距左边和底边留一些边距 + qrX = 192 // 距左边180px + qrY = imgHeight - qrSize - 190 // 距底边100px + + case "invitation": + // invitation类型:精确设置二维码尺寸 + qrSize = 360 // 固定尺寸320px + // 中间偏上位置 + qrX = (imgWidth - qrSize) / 2 // 水平居中 + qrY = 555 // 垂直位置200px + + default: + // 默认(promote样式) + qrSize = 280 // 固定尺寸280px + qrX = 200 // 距左边180px + qrY = imgHeight - qrSize - 200 // 距底边100px + } + + // 8. 生成指定尺寸的二维码图片 + qrCodeImage := qrCode.Image(qrSize) + + // 9. 直接绘制二维码(不添加背景) + dc.DrawImageAnchored(qrCodeImage, qrX+qrSize/2, qrY+qrSize/2, 0.5, 0.5) + + // 11. 输出为字节数组 + var buf bytes.Buffer + err = png.Encode(&buf, dc.Image()) + if err != nil { + logx.Errorf("编码图片失败: %v", err) + return nil, "", fmt.Errorf("编码图片失败: %v", err) + } + + logx.Infof("成功生成带二维码的图片,类型: %s, 二维码内容: %s, 图片尺寸: %dx%d, 二维码尺寸: %dx%d, 位置: (%d,%d)", + qrcodeType, qrcodeUrl, imgWidth, imgHeight, qrSize, qrSize, qrX, qrY) + + return buf.Bytes(), "image/png", nil +} + +// loadImage 加载图片文件 +func (s *ImageService) loadImage(path string) (image.Image, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + // 尝试解码PNG + img, err := png.Decode(file) + if err != nil { + // 如果PNG解码失败,重新打开文件尝试JPEG + file.Close() + file, err = os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + img, err = jpeg.Decode(file) + if err != nil { + // 如果还是失败,使用通用解码器 + file.Close() + file, err = os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + img, _, err = image.Decode(file) + if err != nil { + return nil, err + } + } + } + + return img, nil +} + +// GetSupportedImageTypes 获取支持的图片类型列表 +func (s *ImageService) GetSupportedImageTypes() []string { + return []string{"promote", "invitation"} +} + +// CheckImageExists 检查指定类型的背景图是否存在 +func (s *ImageService) CheckImageExists(qrcodeType string) bool { + var backgroundImageName string + switch qrcodeType { + case "promote": + backgroundImageName = "tg_qrcode_1.jpg" + case "invitation": + backgroundImageName = "yq_qrcode_1.png" + default: + backgroundImageName = "tg_qrcode_1.jpg" + } + + imagePath := filepath.Join(s.baseImagePath, backgroundImageName) + _, err := os.Stat(imagePath) + return err == nil +} diff --git a/app/main/api/internal/service/userService.go b/app/main/api/internal/service/userService.go index 4145a2e..485b61a 100644 --- a/app/main/api/internal/service/userService.go +++ b/app/main/api/internal/service/userService.go @@ -2,22 +2,34 @@ package service import ( "context" + "database/sql" + "qnc-server/app/main/api/internal/config" "qnc-server/app/main/model" + "qnc-server/common/ctxdata" + jwtx "qnc-server/common/jwt" + "qnc-server/common/xerr" "github.com/google/uuid" + "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/stores/sqlx" ) type UserService struct { + Config *config.Config userModel model.UserModel userAuthModel model.UserAuthModel + userTempModel model.UserTempModel + agentModel model.AgentModel } // NewUserService 创建UserService实例 -func NewUserService(userModel model.UserModel, userAuthModel model.UserAuthModel) *UserService { +func NewUserService(config *config.Config, userModel model.UserModel, userAuthModel model.UserAuthModel, userTempModel model.UserTempModel, agentModel model.AgentModel) *UserService { return &UserService{ + Config: config, userModel: userModel, userAuthModel: userAuthModel, + userTempModel: userTempModel, + agentModel: agentModel, } } @@ -63,3 +75,217 @@ func (s *UserService) RegisterUUIDUser(ctx context.Context) (int64, error) { return userId, nil } + +// generalUserToken 生成用户token +func (s *UserService) GeneralUserToken(ctx context.Context, userID int64) (string, error) { + platform, err := ctxdata.GetPlatformFromCtx(ctx) + if err != nil { + return "", err + } + + var isAgent int64 + var agentID int64 + var userType int64 + user, err := s.userModel.FindOne(ctx, userID) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return "", err + } + if user != nil { + userID = user.Id + userType = model.UserTypeNormal + agent, err := s.agentModel.FindOneByUserId(ctx, userID) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return "", err + } + if agent != nil { + agentID = agent.Id + isAgent = model.AgentStatusYes + } + } else { + userTemp, err := s.userTempModel.FindOne(ctx, userID) + if err != nil { + return "", err + } + if userTemp != nil { + userID = userTemp.Id + userType = model.UserTypeTemp + } + } + token, generaErr := jwtx.GenerateJwtToken(jwtx.JwtClaims{ + UserId: userID, + AgentId: agentID, + Platform: platform, + UserType: userType, + IsAgent: isAgent, + }, s.Config.JwtAuth.AccessSecret, s.Config.JwtAuth.AccessExpire) + if generaErr != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新token, 生成token失败 : %d", userID) + } + return token, nil +} + +// RegisterUser 注册用户,返回用户ID +// 传入手机号,自动注册,如果ctx存在临时用户则临时用户转为正式用户 +func (s *UserService) RegisterUser(ctx context.Context, mobile string) (int64, error) { + claims, err := ctxdata.GetClaimsFromCtx(ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return 0, err + } + user, err := s.userModel.FindOneByMobile(ctx, sql.NullString{String: mobile, Valid: true}) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return 0, err + } + if user != nil { + return 0, errors.New("用户已注册") + } + // 普通注册 + if claims == nil { + var userId int64 + err = s.userModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + user := &model.User{ + Mobile: sql.NullString{String: mobile, Valid: true}, + } + result, err := s.userModel.Insert(ctx, session, user) + if err != nil { + return err + } + userId, err = result.LastInsertId() + if err != nil { + return err + } + s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: userId, + AuthType: model.UserAuthTypeMobile, + AuthKey: mobile, + }) + return nil + }) + if err != nil { + return 0, err + } + return userId, nil + } + + // 双重判断是否已经注册 + if claims.UserType == model.UserTypeNormal { + return 0, errors.New("用户已注册") + } + var userId int64 + // 临时转正式注册 + err = s.userModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + user := &model.User{ + Mobile: sql.NullString{String: mobile, Valid: true}, + } + result, err := s.userModel.Insert(ctx, session, user) + if err != nil { + return err + } + userId, err = result.LastInsertId() + if err != nil { + return err + } + _, err = s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: userId, + AuthType: model.UserAuthTypeMobile, + AuthKey: mobile, + }) + if err != nil { + return err + } + err = s.TempUserBindUser(ctx, session, userId) + if err != nil { + return err + } + return nil + }) + if err != nil { + return 0, err + } + return userId, nil +} + +// TempUserBindUser 临时用户绑定用户 +func (s *UserService) TempUserBindUser(ctx context.Context, session sqlx.Session, normalUserID int64) error { + claims, err := ctxdata.GetClaimsFromCtx(ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return err + } + + if claims == nil || claims.UserType != model.UserTypeTemp { + return errors.New("无临时用户") + } + + userTemp, err := s.userTempModel.FindOne(ctx, claims.UserId) + if err != nil { + return err + } + + userAuth, err := s.userAuthModel.FindOneByAuthTypeAuthKey(ctx, userTemp.AuthType, userTemp.AuthKey) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return err + } + if userAuth != nil { + return errors.New("临时用户已注册") + } + + if session == nil { + err := s.userAuthModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + _, err = s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: normalUserID, + AuthType: userTemp.AuthType, + AuthKey: userTemp.AuthKey, + }) + if err != nil { + return err + } + err = s.userTempModel.DeleteSoft(ctx, session, userTemp) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + return nil + } else { + _, err = s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: normalUserID, + AuthType: userTemp.AuthType, + AuthKey: userTemp.AuthKey, + }) + if err != nil { + return err + } + err = s.userTempModel.DeleteSoft(ctx, session, userTemp) + if err != nil { + return err + } + return nil + } + +} + +// _bak_RegisterUUIDUser 注册UUID用户,返回用户ID +func (s *UserService) _bak_RegisterUUIDUser(ctx context.Context) error { + // 生成UUID + uuidStr, err := s.GenerateUUIDUserId(ctx) + if err != nil { + return err + } + + err = s.userTempModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 创建用户临时记录 + userTemp := &model.UserTemp{ + AuthType: model.UserAuthTypeUUID, + AuthKey: uuidStr, + } + _, err := s.userTempModel.Insert(ctx, session, userTemp) + return err + }) + if err != nil { + return err + } + + return nil +} diff --git a/app/main/api/internal/service/wechatpayService.go b/app/main/api/internal/service/wechatpayService.go index d76a092..e1d8d2f 100644 --- a/app/main/api/internal/service/wechatpayService.go +++ b/app/main/api/internal/service/wechatpayService.go @@ -43,7 +43,7 @@ const ( ) type WechatPayService struct { - config config.WxpayConfig + config config.Config wechatClient *core.Client notifyHandler *notify.Handler userAuthModel model.UserAuthModel @@ -96,7 +96,7 @@ func newWechatPayServiceWithPlatformCert(c config.Config, userAuthModel model.Us logx.Infof("微信支付客户端初始化成功(平台证书方式)") return &WechatPayService{ - config: c.Wxpay, + config: c, wechatClient: client, notifyHandler: notifyHandler, userAuthModel: userAuthModel, @@ -147,7 +147,7 @@ func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.Use logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)") return &WechatPayService{ - config: c.Wxpay, + config: c, wechatClient: client, notifyHandler: notifyHandler, userAuthModel: userAuthModel, @@ -160,11 +160,11 @@ func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount floa // 构建支付请求参数 payRequest := app.PrepayRequest{ - Appid: core.String(w.config.AppID), - Mchid: core.String(w.config.MchID), + Appid: core.String(w.config.Wxpay.AppID), + Mchid: core.String(w.config.Wxpay.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), - NotifyUrl: core.String(w.config.NotifyUrl), + NotifyUrl: core.String(w.config.Wxpay.NotifyUrl), Amount: &app.Amount{ Total: core.Int64(totalAmount), }, @@ -189,11 +189,41 @@ func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amo // 构建支付请求参数 payRequest := jsapi.PrepayRequest{ - Appid: core.String(w.config.AppID), - Mchid: core.String(w.config.MchID), + Appid: core.String(w.config.WechatMini.AppID), + Mchid: core.String(w.config.Wxpay.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), - NotifyUrl: core.String(w.config.NotifyUrl), + NotifyUrl: core.String(w.config.Wxpay.NotifyUrl), + Amount: &jsapi.Amount{ + Total: core.Int64(totalAmount), + }, + Payer: &jsapi.Payer{ + Openid: core.String(openid), // 用户的 OpenID,通过前端传入 + }} + + // 初始化 AppApiService + svc := jsapi.JsapiApiService{Client: w.wechatClient} + + // 发起预支付请求 + resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) + if err != nil { + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + } + // 返回预支付交易会话标识 + return resp, nil +} + +// CreateWechatH5Order 创建微信H5支付订单 +func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) { + totalAmount := lzUtils.ToWechatAmount(amount) + + // 构建支付请求参数 + payRequest := jsapi.PrepayRequest{ + Appid: core.String(w.config.WechatH5.AppID), + Mchid: core.String(w.config.Wxpay.MchID), + Description: core.String(description), + OutTradeNo: core.String(outTradeNo), + NotifyUrl: core.String(w.config.Wxpay.NotifyUrl), Amount: &jsapi.Amount{ Total: core.Int64(totalAmount), }, @@ -222,12 +252,12 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64 var err error switch platform { - case "mp-weixin": + case model.PlatformWxMini: userID, getUidErr := ctxdata.GetUidFromCtx(ctx) if getUidErr != nil { return "", getUidErr } - userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMini) + userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID) if findAuthModelErr != nil { return "", findAuthModelErr } @@ -235,20 +265,20 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64 if err != nil { return "", err } - case "h5-weixin": + case model.PlatformWxH5: userID, getUidErr := ctxdata.GetUidFromCtx(ctx) if getUidErr != nil { return "", getUidErr } - userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5) + userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID) if findAuthModelErr != nil { return "", findAuthModelErr } - prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) + prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) if err != nil { return "", err } - case "app": + case model.PlatformApp: // 如果是 APP 平台,调用 APP 支付订单创建 prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo) default: @@ -292,7 +322,7 @@ func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID s // 调用 QueryOrderById 方法查询订单状态 resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{ TransactionId: core.String(transactionID), - Mchid: core.String(w.config.MchID), + Mchid: core.String(w.config.Wxpay.MchID), }) if err != nil { return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode) @@ -314,7 +344,7 @@ func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{ OutTradeNo: core.String(outTradeNo), OutRefundNo: core.String(outRefundNo), - NotifyUrl: core.String(w.config.RefundNotifyUrl), + NotifyUrl: core.String(w.config.Wxpay.RefundNotifyUrl), Amount: &refunddomestic.AmountReq{ Currency: core.String("CNY"), Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)), diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index 409feac..009ef4c 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -21,12 +21,12 @@ type ServiceContext struct { Redis *redis.Redis // 中间件 - SourceInterceptor rest.Middleware - AuthInterceptor rest.Middleware - + AuthInterceptor rest.Middleware + UserAuthInterceptor rest.Middleware // 用户相关模型 UserModel model.UserModel UserAuthModel model.UserAuthModel + UserTempModel model.UserTempModel // 产品相关模型 ProductModel model.ProductModel @@ -95,6 +95,7 @@ type ServiceContext struct { UserService *service.UserService DictService *service.DictService AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService + ImageService *service.ImageService // core service CloudAuthService *cloudauth.CloudAuthClient @@ -104,12 +105,14 @@ type ServiceContext struct { type userModels struct { UserModel model.UserModel UserAuthModel model.UserAuthModel + UserTempModel model.UserTempModel } func initUserModels(db sqlx.SqlConn, redis cache.CacheConf) userModels { return userModels{ UserModel: model.NewUserModel(db, redis), UserAuthModel: model.NewUserAuthModel(db, redis), + UserTempModel: model.NewUserTempModel(db, redis), } } @@ -259,14 +262,14 @@ type services struct { UserService *service.UserService DictService *service.DictService AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService - - CloudAuthService *cloudauth.CloudAuthClient + ImageService *service.ImageService + CloudAuthService *cloudauth.CloudAuthClient } func initServices(c config.Config, userAuthModel model.UserAuthModel, westDexService *service.WestDexService, yushanService *service.YushanService, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, agentModels agentModels, - userModels userModels, adminModels adminModels) services { + userModels userModels, adminModels adminModels, userTempModel model.UserTempModel) services { alipayService := service.NewAliPayService(c) wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey) @@ -282,9 +285,10 @@ func initServices(c config.Config, userAuthModel model.UserAuthModel, westDexSer agentModels.AgentMembershipUserConfigModel, agentModels.AgentProductConfigModel, agentModels.AgentPlatformDeductionModel, agentModels.AgentActiveStatModel, asynqService, agentModels.AgentWithdrawalModel) - userService := service.NewUserService(userModels.UserModel, userModels.UserAuthModel) + userService := service.NewUserService(&c, userModels.UserModel, userModels.UserAuthModel, userTempModel, agentModels.AgentModel) dictService := service.NewDictService(adminModels.AdminDictTypeModel, adminModels.AdminDictDataModel) AdminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminModels.AdminPromotionLinkModel, adminModels.AdminPromotionLinkStatsTotalModel, adminModels.AdminPromotionLinkStatsHistoryModel) + imageService := service.NewImageService() asynqServer := asynq.NewServer( asynq.RedisClientOpt{Addr: c.CacheRedis[0].Host, Password: c.CacheRedis[0].Pass}, asynq.Config{ @@ -322,7 +326,7 @@ func initServices(c config.Config, userAuthModel model.UserAuthModel, westDexSer UserService: userService, DictService: dictService, AdminPromotionLinkStatsService: AdminPromotionLinkStatsService, - + ImageService: imageService, // core service CloudAuthService: cloudAuthService, } @@ -357,18 +361,17 @@ func NewServiceContext(c config.Config) *ServiceContext { // 初始化所有服务 services := initServices(c, userModels.UserAuthModel, westDexService, yushanService, - productModels.FeatureModel, productModels.ProductFeatureModel, agentModels, userModels, adminModels) + productModels.FeatureModel, productModels.ProductFeatureModel, agentModels, userModels, adminModels, userModels.UserTempModel) return &ServiceContext{ - Config: c, - Redis: redisClient, - SourceInterceptor: middleware.NewSourceInterceptorMiddleware().Handle, - AuthInterceptor: middleware.NewAuthInterceptorMiddleware(c).Handle, - + Config: c, + Redis: redisClient, + AuthInterceptor: middleware.NewAuthInterceptorMiddleware(c).Handle, + UserAuthInterceptor: middleware.NewUserAuthInterceptorMiddleware().Handle, // 用户相关模型 UserModel: userModels.UserModel, UserAuthModel: userModels.UserAuthModel, - + UserTempModel: userModels.UserTempModel, // 产品相关模型 ProductModel: productModels.ProductModel, FeatureModel: productModels.FeatureModel, @@ -435,6 +438,7 @@ func NewServiceContext(c config.Config) *ServiceContext { UserService: services.UserService, DictService: services.DictService, AdminPromotionLinkStatsService: services.AdminPromotionLinkStatsService, + ImageService: services.ImageService, // core service CloudAuthService: services.CloudAuthService, diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index 669f56e..f9f042f 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -962,6 +962,9 @@ type BindMobileReq struct { } type BindMobileResp struct { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` } type Commission struct { @@ -1063,6 +1066,11 @@ type FeatureListItem struct { UpdateTime string `json:"update_time"` // 更新时间 } +type GetAgentPromotionQrcodeReq struct { + QrcodeType string `form:"qrcode_type"` + QrcodeUrl string `form:"qrcode_url"` +} + type GetAgentRevenueInfoReq struct { } @@ -1323,17 +1331,6 @@ type MobileCodeLoginResp struct { RefreshAfter int64 `json:"refreshAfter"` } -type MobileLoginReq struct { - Mobile string `json:"mobile" validate:"required,mobile"` - Password string `json:"password" validate:"required"` -} - -type MobileLoginResp struct { - AccessToken string `json:"accessToken"` - AccessExpire int64 `json:"accessExpire"` - RefreshAfter int64 `json:"refreshAfter"` -} - type Notification struct { Title string `json:"title"` // 通知标题 Content string `json:"content"` // 通知内容 (富文本) @@ -1659,18 +1656,6 @@ type RecordLinkClickResp struct { Success bool `json:"success"` // 是否成功 } -type RegisterReq struct { - Mobile string `json:"mobile" validate:"required,mobile"` - Password string `json:"password" validate:"required,min=11,max=11,password"` - Code string `json:"code" validate:"required"` -} - -type RegisterResp struct { - AccessToken string `json:"accessToken"` - AccessExpire int64 `json:"accessExpire"` - RefreshAfter int64 `json:"refreshAfter"` -} - type RejectAuthorizationReq struct { OrderNo string `json:"order_no" validate:"required"` // 订单号 } @@ -1773,6 +1758,7 @@ type User struct { Id int64 `json:"id"` Mobile string `json:"mobile"` NickName string `json:"nickName"` + UserType int64 `json:"userType"` } type UserInfoResp struct { @@ -1790,9 +1776,7 @@ type WXH5AuthResp struct { } type WXMiniAuthReq struct { - Code string `json:"code"` - IV string `json:"iv"` - EncryptedData string `json:"encryptedData"` + Code string `json:"code"` } type WXMiniAuthResp struct { diff --git a/app/main/api/main.go b/app/main/api/main.go index 3bab516..88eebd0 100644 --- a/app/main/api/main.go +++ b/app/main/api/main.go @@ -7,6 +7,7 @@ import ( "os" "qnc-server/app/main/api/internal/config" "qnc-server/app/main/api/internal/handler" + "qnc-server/app/main/api/internal/middleware" "qnc-server/app/main/api/internal/queue" "qnc-server/app/main/api/internal/svc" @@ -26,7 +27,7 @@ func main() { // 根据 ENV 加载不同的配置文件 var defaultConfigFile string if env == "development" { - defaultConfigFile = "app/main//api/etc/main.dev.yaml" + defaultConfigFile = "app/main/api/etc/main.dev.yaml" } else { defaultConfigFile = "etc/main.yaml" } @@ -53,10 +54,9 @@ func main() { } fmt.Println("异步任务启动!!!") }() - // go func() { - // script.RunEncryptMobile() - // }() + server := rest.MustNewServer(c.RestConf) + server.Use(middleware.GlobalSourceInterceptor) defer server.Stop() handler.RegisterHandlers(server, svcContext) diff --git a/app/main/api/static/images/README.md b/app/main/api/static/images/README.md new file mode 100644 index 0000000..2c9a1e8 --- /dev/null +++ b/app/main/api/static/images/README.md @@ -0,0 +1,49 @@ +# 推广二维码背景图片说明 + +## 目录结构 + +``` +static/images/ +├── tg_qrcode_1.jpg # promote 类型的背景图片(推广) +├── yq_qrcode_1.png # invitation 类型的背景图片(邀请) +└── README.md +``` + +## 支持的二维码类型 + +- `promote`: 使用 tg_qrcode_1.jpg 作为背景(推广二维码) +- `invitation`: 使用 yq_qrcode_1.png 作为背景(邀请二维码) + +## 路径计算说明 + +程序运行时的路径计算: + +- 工作目录:`D:\Code\qnc-project\qnc-server` +- 相对路径:`static/images` +- 完整路径示例: + - promote: `D:\Code\qnc-project\qnc-server\static\images\tg_qrcode_1.jpg` + - invitation: `D:\Code\qnc-project\qnc-server\static\images\yq_qrcode_1.png` + +## 背景图片要求 + +- 格式:支持 PNG、JPEG 等常见格式 +- 尺寸:建议不小于 600x600 像素 +- 设计:考虑到二维码会放置在图片中心,请在设计时预留中心区域 + +## API 使用示例 + +```bash +# 生成推广二维码 +GET /api/v1/agent/promotion/qrcode?qrcode_type=promote&qrcode_url=https://example.com/promotion/12345 + +# 生成邀请二维码 +GET /api/v1/agent/promotion/qrcode?qrcode_type=invitation&qrcode_url=https://example.com/invite/67890 +``` + +## 注意事项 + +1. 确保 tg_qrcode_1.jpg 和 yq_qrcode_1.png 文件存在于此目录下 +2. 二维码会自动添加白色半透明背景以确保可读性 +3. 二维码大小会根据背景图片尺寸自动调整,范围在 120px-300px 之间 +4. 默认二维码类型是 `promote` +5. 程序启动时必须在项目根目录(qnc-server)下执行 diff --git a/app/main/api/static/images/image_placeholder.txt b/app/main/api/static/images/image_placeholder.txt new file mode 100644 index 0000000..c5c4a11 Binary files /dev/null and b/app/main/api/static/images/image_placeholder.txt differ diff --git a/app/main/api/static/images/tg_qrcode_1.jpg b/app/main/api/static/images/tg_qrcode_1.jpg new file mode 100644 index 0000000..f961927 Binary files /dev/null and b/app/main/api/static/images/tg_qrcode_1.jpg differ diff --git a/app/main/api/static/images/yq_qrcode_1.png b/app/main/api/static/images/yq_qrcode_1.png new file mode 100644 index 0000000..8854e0f Binary files /dev/null and b/app/main/api/static/images/yq_qrcode_1.png differ diff --git a/app/main/model/vars.go b/app/main/model/vars.go index bd5a3f0..60b3e0a 100644 --- a/app/main/model/vars.go +++ b/app/main/model/vars.go @@ -9,18 +9,24 @@ import ( var ErrNotFound = sqlx.ErrNotFound var ErrNoRowsUpdate = errors.New("update db no rows change") -var UserAuthTypeAppMobile string = "app_mobile" //平台内部 -var UserAuthTypeAppWechat string = "app_wechat" //微信小程序 -var UserAuthTypeH5Mobile string = "h5_mobile" -var UserAuthTypeWxMini string = "wx_mini" -var UserAuthTypeWxh5 string = "wx_h5" -var UserAuthTypeAgentDirect string = "agent_direct" -var UserAuthTypeAgentPromote string = "agent_promote" +// 平台 +var PlatformWxMini string = "wxmini" +var PlatformWxH5 string = "wxh5" +var PlatformApp string = "app" +var PlatformH5 string = "h5" +var PlatformAdmin string = "admin" + +// 用户授权类型 +var UserAuthTypeMobile string = "mobile" +var UserAuthTypeWxMiniOpenID string = "wxmini_openid" +var UserAuthTypeWxh5OpenID string = "wxh5_openid" var UserAuthTypeUUID string = "uuid" +// 代理扣除类型 var AgentDeductionTypeCost string = "cost" var AgentDeductionTypePricing string = "pricing" +// 代理奖励类型 var AgentRewardsTypeDescendantPromotion string = "descendant_promotion" var AgentRewardsTypeDescendantUpgradeVip string = "descendant_upgrade_vip" var AgentRewardsTypeDescendantUpgradeSvip string = "descendant_upgrade_svip" @@ -28,10 +34,12 @@ var AgentRewardsTypeDescendantStayActive string = "descendant_stay_active" var AgentRewardsTypeDescendantNewActive string = "descendant_new_active" var AgentRewardsTypeDescendantWithdraw string = "descendant_withdraw" +// 代理等级名称 var AgentLeveNameNormal string = "normal" var AgentLeveNameVIP string = "VIP" var AgentLeveNameSVIP string = "SVIP" +// 订单状态 const ( OrderStatusPending = "pending" OrderStatusPaid = "paid" @@ -39,12 +47,16 @@ const ( OrderStatusRefunded = "refunded" OrderStatusClosed = "closed" ) + +// 订单退款状态 const ( OrderRefundStatusPending = "pending" OrderRefundStatusSuccess = "success" OrderRefundStatusFailed = "failed" OrderRefundStatusClosed = "closed" ) + +// 查询状态 const ( QueryStatePending = "pending" QueryStateFailed = "failed" @@ -53,10 +65,13 @@ const ( QueryStateCleaned = "cleaned" ) +// 授权类型 const ( AuthorizationGrantTypeFace = "face" AuthorizationGrantTypeSms = "sms" ) + +// 授权状态 const ( AuthorizationStatusPending = "pending" AuthorizationStatusSuccess = "success" @@ -66,14 +81,29 @@ const ( AuthorizationStatusRejected = "rejected" ) +// 授权人脸状态 const ( AuthorizationFaceStatusPending = "pending" AuthorizationFaceStatusSuccess = "success" AuthorizationFaceStatusFailed = "failed" ) +// 代理实名状态 const ( AgentRealNameStatusPending = "pending" AgentRealNameStatusApproved = "approved" AgentRealNameStatusRejected = "rejected" ) + +// 用户身份类型 +const ( + UserTypeTemp = 0 // 临时用户 + UserTypeNormal = 1 // 正式用户 + UserTypeAdmin = 2 // 管理员 +) + +// 代理状态 +const ( + AgentStatusNo = 0 // 非代理 + AgentStatusYes = 1 // 是代理 +) diff --git a/common/ctxdata/ctxData.go b/common/ctxdata/ctxData.go index 10782f5..5c6f45f 100644 --- a/common/ctxdata/ctxData.go +++ b/common/ctxdata/ctxData.go @@ -5,14 +5,16 @@ import ( "encoding/json" "errors" "fmt" + "qnc-server/app/main/model" + jwtx "qnc-server/common/jwt" ) const CtxKeyJwtUserId = "userId" // 定义错误类型 var ( - ErrNoUserIdInCtx = errors.New("上下文中没有用户ID") // 未登录 - ErrInvalidUserId = errors.New("用户ID格式无效") // 数据异常 + ErrNoInCtx = errors.New("上下文中没有相关数据") + ErrInvalidUserId = errors.New("用户ID格式无效") // 数据异常 ) // GetUidFromCtx 从 context 中获取用户 ID @@ -20,7 +22,11 @@ func GetUidFromCtx(ctx context.Context) (int64, error) { // 尝试从上下文中获取 jwtUserId value := ctx.Value(CtxKeyJwtUserId) if value == nil { - return 0, ErrNoUserIdInCtx + claims, err := GetClaimsFromCtx(ctx) + if err != nil { + return 0, err + } + return claims.UserId, nil } // 根据值的类型进行不同处理 @@ -47,12 +53,52 @@ func GetUidFromCtx(ctx context.Context) (int64, error) { } } +func GetClaimsFromCtx(ctx context.Context) (*jwtx.JwtClaims, error) { + value := ctx.Value(jwtx.ExtraKey) + if value == nil { + return nil, ErrNoInCtx + } + + // 首先尝试直接断言为 *jwtx.JwtClaims + if claims, ok := value.(*jwtx.JwtClaims); ok { + return claims, nil + } + + // 如果直接断言失败,尝试从 map[string]interface{} 中解析 + if claimsMap, ok := value.(map[string]interface{}); ok { + return jwtx.MapToJwtClaims(claimsMap) + } + + return nil, ErrNoInCtx +} + // IsNoUserIdError 判断是否是未登录错误 func IsNoUserIdError(err error) bool { - return errors.Is(err, ErrNoUserIdInCtx) + return errors.Is(err, ErrNoInCtx) } // IsInvalidUserIdError 判断是否是用户ID格式错误 func IsInvalidUserIdError(err error) bool { return errors.Is(err, ErrInvalidUserId) } + +// GetPlatformFromCtx 从 context 中获取平台 +func GetPlatformFromCtx(ctx context.Context) (string, error) { + platform, platformOk := ctx.Value("platform").(string) + if !platformOk { + return "", fmt.Errorf("平台不存在: %s", platform) + } + + switch platform { + case model.PlatformWxMini: + return model.PlatformWxMini, nil + case model.PlatformWxH5: + return model.PlatformWxH5, nil + case model.PlatformApp: + return model.PlatformApp, nil + case model.PlatformH5: + return model.PlatformH5, nil + default: + return "", fmt.Errorf("不支持的支付平台: %s", platform) + } +} diff --git a/common/jwt/jwtx.go b/common/jwt/jwtx.go index bae8de9..bc8be83 100644 --- a/common/jwt/jwtx.go +++ b/common/jwt/jwtx.go @@ -1,68 +1,94 @@ package jwtx import ( + "encoding/json" "errors" - "github.com/golang-jwt/jwt/v4" - "strconv" "time" + + "github.com/golang-jwt/jwt/v4" ) -// Token 生成逻辑的函数,接收 userId、过期时间和密钥,返回生成的 token -func GenerateJwtToken(userId int64, secret string, expireTime int64) (string, error) { - // 获取当前时间戳 - now := time.Now().Unix() - // 定义 JWT Claims - claims := jwt.MapClaims{ - "exp": now + expireTime, // token 过期时间 - "iat": now, // 签发时间 - "userId": userId, // 用户ID +const ExtraKey = "extra" + +type JwtClaims struct { + UserId int64 `json:"userId"` + AgentId int64 `json:"agentId"` + Platform string `json:"platform"` + // 用户身份类型:0-临时用户,1-正式用户 + UserType int64 `json:"userType"` + // 是否代理:0-否,1-是 + IsAgent int64 `json:"isAgent"` +} + +// MapToJwtClaims 将 map[string]interface{} 转换为 JwtClaims 结构体 +func MapToJwtClaims(claimsMap map[string]interface{}) (*JwtClaims, error) { + // 使用JSON序列化/反序列化的方式自动转换 + jsonData, err := json.Marshal(claimsMap) + if err != nil { + return nil, errors.New("序列化claims失败") } - // 创建新的 JWT token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + var claims JwtClaims + if err := json.Unmarshal(jsonData, &claims); err != nil { + return nil, errors.New("反序列化claims失败") + } - // 使用密钥对 token 签名 - signedToken, err := token.SignedString([]byte(secret)) + return &claims, nil +} + +// GenerateJwtToken 生成JWT token +func GenerateJwtToken(claims JwtClaims, secret string, expire int64) (string, error) { + now := time.Now().Unix() + + // 将 claims 结构体转换为 map[string]interface{} + claimsBytes, err := json.Marshal(claims) if err != nil { return "", err } - return signedToken, nil + var claimsMap map[string]interface{} + if err := json.Unmarshal(claimsBytes, &claimsMap); err != nil { + return "", err + } + + jwtClaims := jwt.MapClaims{ + "exp": now + expire, + "iat": now, + "userId": claims.UserId, + ExtraKey: claimsMap, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims) + return token.SignedString([]byte(secret)) } -func ParseJwtToken(tokenStr string, secret string) (int64, error) { + +func ParseJwtToken(tokenStr string, secret string) (*JwtClaims, error) { token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { return []byte(secret), nil }) if err != nil || !token.Valid { - return 0, errors.New("invalid JWT") + return nil, errors.New("invalid JWT") } claims, ok := token.Claims.(jwt.MapClaims) if !ok || !token.Valid { - return 0, errors.New("invalid JWT claims") + return nil, errors.New("invalid JWT claims") } - // 从 claims 中提取 userId - userIdRaw, ok := claims["userId"] - if !ok { - return 0, errors.New("userId not found in JWT") + extraInfo, exists := claims[ExtraKey] + if !exists { + return nil, errors.New("extra not found in JWT") } - // 处理不同类型的 userId,确保它被转换为 int64 - switch userId := userIdRaw.(type) { - case float64: - return int64(userId), nil - case int64: - return userId, nil - case string: - // 如果 userId 是字符串,可以尝试将其转换为 int64 - parsedId, err := strconv.ParseInt(userId, 10, 64) - if err != nil { - return 0, errors.New("invalid userId in JWT") - } - return parsedId, nil - default: - return 0, errors.New("unsupported userId type in JWT") + // 尝试直接断言为 JwtClaims 结构体 + if jwtClaims, ok := extraInfo.(JwtClaims); ok { + return &jwtClaims, nil } + + // 尝试从 map[string]interface{} 中解析 + if claimsMap, ok := extraInfo.(map[string]interface{}); ok { + return MapToJwtClaims(claimsMap) + } + + return nil, errors.New("unsupported extra type in JWT") } diff --git a/common/jwt/jwtx_test.go b/common/jwt/jwtx_test.go deleted file mode 100644 index 49c506c..0000000 --- a/common/jwt/jwtx_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package jwtx - -import ( - "fmt" - "testing" - "time" -) - -func TestGenerateAndParseJwtToken(t *testing.T) { - // 测试参数 - userId := int64(39) - secret := "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" - expireTime := int64(2592000) // 1小时过期 - - // 生成token - token, err := GenerateJwtToken(userId, secret, expireTime) - if err != nil { - t.Fatalf("生成JWT令牌失败: %v", err) - } - if token == "" { - t.Fatal("生成的JWT令牌为空") - } - fmt.Println(token) - // 解析token - parsedUserId, err := ParseJwtToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTA5MjYyNTgsImlhdCI6MTc0ODMzNDI1OCwidXNlcklkIjoxNDN9.V4qV3dAjE6G-xm0KmB6QHCVhy2SmRDGAWcvl32hLNmI", secret) - if err != nil { - t.Fatalf("解析JWT令牌失败: %v", err) - } - fmt.Printf("解析出的userId: %d\n", parsedUserId) - // 验证解析出的userId是否正确 - // if parsedUserId != userId { - // t.Errorf("解析出的userId不匹配: 期望 %d, 实际 %d", userId, parsedUserId) - // } -} - -func TestTokenExpiration(t *testing.T) { - // 测试参数 - userId := int64(10086) - secret := "test_secret_key" - expireTime := int64(1) // 1秒过期 - - // 生成token - token, err := GenerateJwtToken(userId, secret, expireTime) - if err != nil { - t.Fatalf("生成JWT令牌失败: %v", err) - } - - // 等待令牌过期 - time.Sleep(2 * time.Second) - - // 解析已过期token - _, err = ParseJwtToken(token, secret) - if err == nil { - t.Error("期望令牌过期错误,但没有发生错误") - } -} - -func TestInvalidToken(t *testing.T) { - secret := "test_secret_key" - - // 测试无效token - invalidToken := "invalid.token.string" - _, err := ParseJwtToken(invalidToken, secret) - if err == nil { - t.Error("期望无效令牌错误,但没有发生错误") - } - - // 测试密钥不匹配 - userId := int64(10086) - expireTime := int64(3600) - token, _ := GenerateJwtToken(userId, "original_secret", expireTime) - _, err = ParseJwtToken(token, "wrong_secret") - if err == nil { - t.Error("期望密钥不匹配错误,但没有发生错误") - } -} - -func TestUserIdTypes(t *testing.T) { - cases := []struct { - name string - setupFn func() (string, string, int64) - expected int64 - }{ - { - name: "正常int64类型", - setupFn: func() (string, string, int64) { - userId := int64(10086) - secret := "test_secret" - expireTime := int64(3600) - token, _ := GenerateJwtToken(userId, secret, expireTime) - return token, secret, userId - }, - expected: 10086, - }, - // 其他类型在实际场景中通过手动修改token内容测试,这里省略 - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - token, secret, expected := tc.setupFn() - userId, err := ParseJwtToken(token, secret) - if err != nil { - t.Fatalf("解析失败: %v", err) - } - if userId != expected { - t.Errorf("用户ID不匹配: 期望 %d, 实际 %d", expected, userId) - } - }) - } -} diff --git a/common/xerr/errCode.go b/common/xerr/errCode.go index 4d872e0..2e53ecc 100644 --- a/common/xerr/errCode.go +++ b/common/xerr/errCode.go @@ -6,17 +6,44 @@ const OK uint32 = 200 /**(前3位代表业务,后三位代表具体功能)**/ // 全局错误码 +// 服务器通用错误 const SERVER_COMMON_ERROR uint32 = 100001 + +// 请求参数错误 const REUQEST_PARAM_ERROR uint32 = 100002 + +// token过期错误 const TOKEN_EXPIRE_ERROR uint32 = 100003 + +// token生成错误 const TOKEN_GENERATE_ERROR uint32 = 100004 + +// 数据库错误 const DB_ERROR uint32 = 100005 + +// 数据库更新影响行数为0错误 const DB_UPDATE_AFFECTED_ZERO_ERROR uint32 = 100006 + +// 参数验证错误 const PARAM_VERIFICATION_ERROR uint32 = 100007 + +// 自定义错误 const CUSTOM_ERROR uint32 = 100008 + +// 用户不存在错误 const USER_NOT_FOUND uint32 = 100009 +// 用户需要绑定手机号 +const USER_NEED_BIND_MOBILE uint32 = 100010 + +// 登录失败错误 const LOGIN_FAILED uint32 = 200001 + +// 查询等待中 const LOGIC_QUERY_WAIT uint32 = 200002 + +// 查询错误 const LOGIC_QUERY_ERROR uint32 = 200003 + +// 查询结果不存在 const LOGIC_QUERY_NOT_FOUND uint32 = 200004 diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 index 6a8c38c..4d64131 100644 --- a/deploy/script/gen_models.ps1 +++ b/deploy/script/gen_models.ps1 @@ -36,9 +36,10 @@ $tables = @( # "query_cleanup_config" # "user" # "user_auth" + "user_temp" # "example" - "authorization" - "authorization_face" + # "authorization" + # "authorization_face" # "admin_user" # "admin_user_role" # "admin_api", diff --git a/deploy/sql/user.sql b/deploy/sql/user.sql index 13affd4..3a3d6b9 100644 --- a/deploy/sql/user.sql +++ b/deploy/sql/user.sql @@ -1,43 +1,60 @@ SET NAMES utf8mb4; + SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for main -- ---------------------------- DROP TABLE IF EXISTS `user`; + CREATE TABLE `user` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `del_state` tinyint NOT NULL DEFAULT '0', - `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', - `mobile` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', - `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, - `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', - `info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', - `inside` tinyint NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `unique_mobile` (`mobile`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; + `id` bigint NOT NULL AUTO_INCREMENT, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `mobile` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `inside` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `unique_mobile` (`mobile`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表'; -- ---------------------------- -- Table structure for user_auth -- ---------------------------- DROP TABLE IF EXISTS `user_auth`; + CREATE TABLE `user_auth` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `del_state` tinyint NOT NULL DEFAULT '0', - `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', - `user_id` bigint NOT NULL DEFAULT '0', - `auth_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台唯一id', - `auth_type` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台类型', - PRIMARY KEY (`id`), - UNIQUE KEY `unique_type_key` (`auth_type`,`auth_key`) USING BTREE, - UNIQUE KEY `unique_userId_key` (`user_id`,`auth_type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户授权表'; + `id` bigint NOT NULL AUTO_INCREMENT, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `user_id` bigint NOT NULL DEFAULT '0', + `auth_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台唯一id', + `auth_type` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台类型', + PRIMARY KEY (`id`), + UNIQUE KEY `unique_type_key` (`auth_type`, `auth_key`) USING BTREE, + UNIQUE KEY `unique_userId_key` (`user_id`, `auth_type`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户授权表'; SET FOREIGN_KEY_CHECKS = 1; + +-- 临时用户表(user_temp) +CREATE TABLE `user_temp` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `auth_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台唯一id', + `auth_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台类型', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + PRIMARY KEY (`id`), + UNIQUE KEY `unique_type_key` (`auth_type`, `auth_key`) USING BTREE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '临时用户表'; \ No newline at end of file diff --git a/go.mod b/go.mod index c8fd893..2c2d2f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module qnc-server -go 1.22.4 +go 1.23.0 + +toolchain go1.23.4 require ( github.com/Masterminds/squirrel v1.5.4 @@ -12,6 +14,7 @@ require ( github.com/aliyun/credentials-go v1.4.6 github.com/bytedance/sonic v1.13.0 github.com/cenkalti/backoff/v4 v4.3.0 + github.com/fogleman/gg v1.3.0 github.com/go-playground/validator/v10 v10.22.1 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.6.0 @@ -21,6 +24,7 @@ require ( github.com/redis/go-redis/v9 v9.7.0 github.com/samber/lo v1.50.0 github.com/shopspring/decimal v1.4.0 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/smartwalle/alipay/v3 v3.2.23 github.com/sony/sonyflake v1.2.0 github.com/tidwall/gjson v1.18.0 @@ -55,6 +59,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect @@ -96,9 +101,10 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/image v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/go.sum b/go.sum index 161f0f0..5a5ac71 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -135,6 +137,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -236,6 +240,8 @@ github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE= github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE= github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8= @@ -333,6 +339,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= +golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -403,8 +411,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/static/images/README.md b/static/images/README.md new file mode 100644 index 0000000..2c9a1e8 --- /dev/null +++ b/static/images/README.md @@ -0,0 +1,49 @@ +# 推广二维码背景图片说明 + +## 目录结构 + +``` +static/images/ +├── tg_qrcode_1.jpg # promote 类型的背景图片(推广) +├── yq_qrcode_1.png # invitation 类型的背景图片(邀请) +└── README.md +``` + +## 支持的二维码类型 + +- `promote`: 使用 tg_qrcode_1.jpg 作为背景(推广二维码) +- `invitation`: 使用 yq_qrcode_1.png 作为背景(邀请二维码) + +## 路径计算说明 + +程序运行时的路径计算: + +- 工作目录:`D:\Code\qnc-project\qnc-server` +- 相对路径:`static/images` +- 完整路径示例: + - promote: `D:\Code\qnc-project\qnc-server\static\images\tg_qrcode_1.jpg` + - invitation: `D:\Code\qnc-project\qnc-server\static\images\yq_qrcode_1.png` + +## 背景图片要求 + +- 格式:支持 PNG、JPEG 等常见格式 +- 尺寸:建议不小于 600x600 像素 +- 设计:考虑到二维码会放置在图片中心,请在设计时预留中心区域 + +## API 使用示例 + +```bash +# 生成推广二维码 +GET /api/v1/agent/promotion/qrcode?qrcode_type=promote&qrcode_url=https://example.com/promotion/12345 + +# 生成邀请二维码 +GET /api/v1/agent/promotion/qrcode?qrcode_type=invitation&qrcode_url=https://example.com/invite/67890 +``` + +## 注意事项 + +1. 确保 tg_qrcode_1.jpg 和 yq_qrcode_1.png 文件存在于此目录下 +2. 二维码会自动添加白色半透明背景以确保可读性 +3. 二维码大小会根据背景图片尺寸自动调整,范围在 120px-300px 之间 +4. 默认二维码类型是 `promote` +5. 程序启动时必须在项目根目录(qnc-server)下执行 diff --git a/static/images/image_placeholder.txt b/static/images/image_placeholder.txt new file mode 100644 index 0000000..c5c4a11 Binary files /dev/null and b/static/images/image_placeholder.txt differ diff --git a/static/images/tg_qrcode_1.jpg b/static/images/tg_qrcode_1.jpg new file mode 100644 index 0000000..f961927 Binary files /dev/null and b/static/images/tg_qrcode_1.jpg differ diff --git a/static/images/yq_qrcode_1.png b/static/images/yq_qrcode_1.png new file mode 100644 index 0000000..8854e0f Binary files /dev/null and b/static/images/yq_qrcode_1.png differ