package request import ( "bytes" "context" "io" "lone-services/pkg/utils" "net/http" "net/url" "strconv" "strings" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) type ctxKey string const ( CtxUserId ctxKey = "X-User-Id" CtxUserName ctxKey = "X-User-Name" CtxRefresh ctxKey = "X-Refresh" CtxClientIP ctxKey = "X-Client-Ip" CtxUserAgent ctxKey = "X-User-Agent" CtxClientCode ctxKey = "X-Client-Code" CtxSaleId ctxKey = "X-Sale-Id" CtxSaleName ctxKey = "X-Sale-Name" CtxSaleMobile ctxKey = "X-Sale-Mobile" CtxSaleProvince ctxKey = "X-Sale-Province" CtxStoreId ctxKey = "X-Store-Id" CtxStoreName ctxKey = "X-Store-Name" CtxGroupId ctxKey = "X-Group-Id" CtxGroupName ctxKey = "X-Group-Name" CtxUserType ctxKey = "X-User-Type" CtxCallbackRawBody ctxKey = "callback_raw_body" CtxCallbackHeaders ctxKey = "callback_headers" CtxCallbackErr ctxKey = "callback_read_err" ) var callbackPrefixes = []string{ "/api/v3/express/", "/api/v3/other1/", "/api/v3/other2/", } type UserInfo struct { ID int64 Name string RawUID string Refresh string ClientIP string UserAgent string Type string ClientCode string SaleId string SaleName string SaleMobile string SaleProvince string StoreId string StoreName string GroupId string GroupName string Valid bool } func GetUserInfo(ctx context.Context) UserInfo { rawUID, _ := ctx.Value(CtxUserId).(string) name, _ := ctx.Value(CtxUserName).(string) refresh, _ := ctx.Value(CtxRefresh).(string) clientIP, _ := ctx.Value(CtxClientIP).(string) userAgent, _ := ctx.Value(CtxUserAgent).(string) userType, _ := ctx.Value(CtxUserType).(string) clientCode, _ := ctx.Value(CtxClientCode).(string) saleId, _ := ctx.Value(CtxSaleId).(string) saleName, _ := ctx.Value(CtxSaleName).(string) saleMobile, _ := ctx.Value(CtxSaleMobile).(string) saleProvince, _ := ctx.Value(CtxSaleProvince).(string) storeId, _ := ctx.Value(CtxStoreId).(string) storeName, _ := ctx.Value(CtxStoreName).(string) groupId, _ := ctx.Value(CtxGroupId).(string) groupName, _ := ctx.Value(CtxGroupName).(string) info := UserInfo{ RawUID: rawUID, Name: name, Refresh: refresh, ClientIP: clientIP, UserAgent: userAgent, Type: userType, ClientCode: clientCode, SaleId: saleId, SaleName: saleName, SaleMobile: saleMobile, SaleProvince: saleProvince, StoreId: storeId, StoreName: storeName, GroupId: groupId, GroupName: groupName, } if rawUID == "" { return info } uid, err := strconv.ParseInt(rawUID, 10, 64) if err != nil { info.Valid = false return info } info.ID = uid info.Valid = true return info } func getRealClientIP(r *http.Request) string { xff := r.Header.Get("X-Forwarded-For") if xff != "" { return xff } xri := r.Header.Get("X-Real-Ip") if xri != "" { return xri } return r.RemoteAddr } func UserReadMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { newCtx := r.Context() if isCallbackPath(r.URL.Path) { body, err := io.ReadAll(r.Body) if err != nil { newCtx = context.WithValue(newCtx, CtxCallbackRawBody, []byte(nil)) newCtx = context.WithValue(newCtx, CtxCallbackHeaders, http.Header(nil)) newCtx = context.WithValue(newCtx, CtxCallbackErr, err) } else { r.Body = io.NopCloser(bytes.NewBuffer(body)) header := make(http.Header) for k, vv := range r.Header { header[k] = vv } newCtx = context.WithValue(newCtx, CtxCallbackRawBody, body) newCtx = context.WithValue(newCtx, CtxCallbackHeaders, header) newCtx = context.WithValue(newCtx, CtxCallbackErr, error(nil)) } next(w, r.WithContext(newCtx)) return } newCtx = context.WithValue(newCtx, CtxUserId, r.Header.Get("X-User-Id")) newCtx = context.WithValue(newCtx, CtxUserName, r.Header.Get("X-User-Name")) newCtx = context.WithValue(newCtx, CtxRefresh, r.Header.Get("X-Refresh")) newCtx = context.WithValue(newCtx, CtxClientIP, getRealClientIP(r)) newCtx = context.WithValue(newCtx, CtxUserAgent, r.UserAgent()) newCtx = context.WithValue(newCtx, CtxUserType, r.Header.Get("X-User-Type")) newCtx = context.WithValue(newCtx, CtxClientCode, r.Header.Get("X-Client-Code")) newCtx = context.WithValue(newCtx, CtxSaleId, r.Header.Get("X-Sale-Id")) newCtx = context.WithValue(newCtx, CtxSaleName, r.Header.Get("X-Sale-Name")) newCtx = context.WithValue(newCtx, CtxSaleMobile, r.Header.Get("X-Sale-Mobile")) newCtx = context.WithValue(newCtx, CtxSaleProvince, r.Header.Get("X-Sale-Province")) newCtx = context.WithValue(newCtx, CtxStoreId, r.Header.Get("X-Store-Id")) newCtx = context.WithValue(newCtx, CtxStoreName, r.Header.Get("X-Store-Name")) newCtx = context.WithValue(newCtx, CtxGroupId, r.Header.Get("X-Group-Id")) newCtx = context.WithValue(newCtx, CtxGroupName, r.Header.Get("X-Group-Name")) next(w, r.WithContext(newCtx)) } } func isCallbackPath(path string) bool { for _, prefix := range callbackPrefixes { if strings.HasPrefix(path, prefix) { return true } } return false } func setMDIfNotEmpty(md metadata.MD, key, value string) { if value != utils.StringEmpty { md.Set(key, value) } } func UserClientInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { user := GetUserInfo(ctx) md := metadata.New(map[string]string{}) if user.Valid { md.Set("x-user-id", user.RawUID) md.Set("x-user-name", url.QueryEscape(user.Name)) } setMDIfNotEmpty(md, "x-refresh", user.Refresh) setMDIfNotEmpty(md, "x-client-ip", user.ClientIP) if user.UserAgent != utils.StringEmpty { md.Set("x-user-agent", url.QueryEscape(user.UserAgent)) } setMDIfNotEmpty(md, "x-user-type", user.Type) setMDIfNotEmpty(md, "x-client-code", user.ClientCode) setMDIfNotEmpty(md, "x-sale-id", user.SaleId) if user.SaleName != utils.StringEmpty { md.Set("x-sale-name", url.QueryEscape(user.SaleName)) } setMDIfNotEmpty(md, "x-sale-mobile", user.SaleMobile) setMDIfNotEmpty(md, "x-sale-province", user.SaleProvince) setMDIfNotEmpty(md, "x-store-id", user.StoreId) if user.StoreName != utils.StringEmpty { md.Set("x-store-name", url.QueryEscape(user.StoreName)) } setMDIfNotEmpty(md, "x-group-id", user.GroupId) if user.GroupName != utils.StringEmpty { md.Set("x-group-name", url.QueryEscape(user.GroupName)) } ctx = metadata.NewOutgoingContext(ctx, md) return invoker(ctx, method, req, reply, cc, opts...) }