diff --git a/config/config.dev.yaml b/config/config.dev.yaml index 1007bfe..e945525 100644 --- a/config/config.dev.yaml +++ b/config/config.dev.yaml @@ -29,3 +29,8 @@ serve: address: "10.0.40.199" port: 8602 ttl: 20 + +wxMini: + appid: "wx1b6ea981a8bafd4e" + secret: "e62e0fdf51db0e91dee3a7aba6a9c3ed" + envVersion: "release" \ No newline at end of file diff --git a/config/config.go b/config/config.go index 17e398b..0367e45 100644 --- a/config/config.go +++ b/config/config.go @@ -5,10 +5,15 @@ import "git.hlsq.asia/mmorpg/service-common/config" const path = "./config" type Config struct { - App *config.AppConfig `yaml:"app"` - Log *config.LogConfig `yaml:"log"` - DB *config.DBConfig `yaml:"db"` - Serve *config.ServeConfig `yaml:"serve"` + App *config.AppConfig `yaml:"app"` + Log *config.LogConfig `yaml:"log"` + DB *config.DBConfig `yaml:"db"` + Serve *config.ServeConfig `yaml:"serve"` + WxMini *struct { + AppID string `yaml:"appid"` + Secret string `yaml:"secret"` + EnvVersion string `yaml:"envVersion"` + } `yaml:"wxMini"` } var cfg *Config diff --git a/config/config.prod.yaml b/config/config.prod.yaml index 784546a..efc47e2 100644 --- a/config/config.prod.yaml +++ b/config/config.prod.yaml @@ -29,3 +29,8 @@ serve: address: "172.18.28.0" port: 8602 ttl: 20 + +wxMini: + appid: "wx1b6ea981a8bafd4e" + secret: "e62e0fdf51db0e91dee3a7aba6a9c3ed" + envVersion: "release" \ No newline at end of file diff --git a/go.mod b/go.mod index 2226b4b..29af1a6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.hlsq.asia/mmorpg/service-user go 1.23.1 require ( - git.hlsq.asia/mmorpg/service-common v0.0.0-20260113014617-7812a3c669d7 + git.hlsq.asia/mmorpg/service-common v0.0.0-20260113095415-34b2e45e3d50 github.com/judwhite/go-svc v1.2.1 google.golang.org/grpc v1.71.1 gorm.io/gen v0.3.27 diff --git a/go.sum b/go.sum index 6a84cac..7a6b9fa 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.hlsq.asia/mmorpg/service-common v0.0.0-20260113014617-7812a3c669d7 h1:sNZWEsAy4G5HEigdnnIHQ4eqmifN3rDPPApeTG4c4h4= -git.hlsq.asia/mmorpg/service-common v0.0.0-20260113014617-7812a3c669d7/go.mod h1:xv6m1I2jUA6mudKVznygpnzMoshBQarthHD1QnkW4qc= +git.hlsq.asia/mmorpg/service-common v0.0.0-20260113095415-34b2e45e3d50 h1:T0JZl2N+HMYRgyifEGFXD2y1MZOuoAS1xjnIg/JLGwM= +git.hlsq.asia/mmorpg/service-common v0.0.0-20260113095415-34b2e45e3d50/go.mod h1:xv6m1I2jUA6mudKVznygpnzMoshBQarthHD1QnkW4qc= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= diff --git a/internal/dao/model/users.gen.go b/internal/dao/model/users.gen.go index 5e0a675..e5905db 100644 --- a/internal/dao/model/users.gen.go +++ b/internal/dao/model/users.gen.go @@ -15,13 +15,15 @@ const TableNameUser = "users" // User mapped from table type User struct { - ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` - Sn string `gorm:"column:sn;not null;comment:业务唯一编号" json:"sn"` // 业务唯一编号 - Name string `gorm:"column:name;not null" json:"name"` - Phone string `gorm:"column:phone;not null" json:"phone"` - CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;not null" json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` + ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` + Sn string `gorm:"column:sn;not null;comment:业务唯一编号" json:"sn"` // 业务唯一编号 + Name string `gorm:"column:name;not null" json:"name"` + Phone string `gorm:"column:phone" json:"phone"` + WxUnionID string `gorm:"column:wx_union_id;comment:微信用户唯一标识" json:"wx_union_id"` // 微信用户唯一标识 + WxMiniOpenID string `gorm:"column:wx_mini_open_id;comment:微信小程序的openID" json:"wx_mini_open_id"` // 微信小程序的openID + CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;not null" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` } // TableName User's table name diff --git a/internal/dao/query/users.gen.go b/internal/dao/query/users.gen.go index a96bb38..f047106 100644 --- a/internal/dao/query/users.gen.go +++ b/internal/dao/query/users.gen.go @@ -31,6 +31,8 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user { _user.Sn = field.NewString(tableName, "sn") _user.Name = field.NewString(tableName, "name") _user.Phone = field.NewString(tableName, "phone") + _user.WxUnionID = field.NewString(tableName, "wx_union_id") + _user.WxMiniOpenID = field.NewString(tableName, "wx_mini_open_id") _user.CreatedAt = field.NewTime(tableName, "created_at") _user.UpdatedAt = field.NewTime(tableName, "updated_at") _user.DeletedAt = field.NewField(tableName, "deleted_at") @@ -43,14 +45,16 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user { type user struct { userDo userDo - ALL field.Asterisk - ID field.Uint64 - Sn field.String // 业务唯一编号 - Name field.String - Phone field.String - CreatedAt field.Time - UpdatedAt field.Time - DeletedAt field.Field + ALL field.Asterisk + ID field.Uint64 + Sn field.String // 业务唯一编号 + Name field.String + Phone field.String + WxUnionID field.String // 微信用户唯一标识 + WxMiniOpenID field.String // 微信小程序的openID + CreatedAt field.Time + UpdatedAt field.Time + DeletedAt field.Field fieldMap map[string]field.Expr } @@ -71,6 +75,8 @@ func (u *user) updateTableName(table string) *user { u.Sn = field.NewString(table, "sn") u.Name = field.NewString(table, "name") u.Phone = field.NewString(table, "phone") + u.WxUnionID = field.NewString(table, "wx_union_id") + u.WxMiniOpenID = field.NewString(table, "wx_mini_open_id") u.CreatedAt = field.NewTime(table, "created_at") u.UpdatedAt = field.NewTime(table, "updated_at") u.DeletedAt = field.NewField(table, "deleted_at") @@ -98,11 +104,13 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (u *user) fillFieldMap() { - u.fieldMap = make(map[string]field.Expr, 7) + u.fieldMap = make(map[string]field.Expr, 9) u.fieldMap["id"] = u.ID u.fieldMap["sn"] = u.Sn u.fieldMap["name"] = u.Name u.fieldMap["phone"] = u.Phone + u.fieldMap["wx_union_id"] = u.WxUnionID + u.fieldMap["wx_mini_open_id"] = u.WxMiniOpenID u.fieldMap["created_at"] = u.CreatedAt u.fieldMap["updated_at"] = u.UpdatedAt u.fieldMap["deleted_at"] = u.DeletedAt diff --git a/internal/dao/repository/users.go b/internal/dao/repository/users.go index d8aa2f8..aab8799 100644 --- a/internal/dao/repository/users.go +++ b/internal/dao/repository/users.go @@ -82,3 +82,18 @@ func (d *UserDao) FindByPhone(phone string) (*model.User, error) { } return first, nil } + +// FindByWxUnionIDOrOpenID 通过微信unionID或openID查找用户 +func (d *UserDao) FindByWxUnionIDOrOpenID(unionID, openID string) (*model.User, error) { + q := d.query.User.WithContext(d.ctx) + if unionID != "" { + q = q.Where(d.query.User.WxUnionID.Eq(unionID)) + } else { + q = q.Where(d.query.User.WxMiniOpenID.Eq(openID)) + } + first, err := q.First() + if err != nil { + return nil, err + } + return first, nil +} diff --git a/internal/grpc_server/server/server.go b/internal/grpc_server/server/server.go index eedc68e..b7dc877 100644 --- a/internal/grpc_server/server/server.go +++ b/internal/grpc_server/server/server.go @@ -10,10 +10,11 @@ import ( "git.hlsq.asia/mmorpg/service-common/utils" "git.hlsq.asia/mmorpg/service-user/internal/dao/model" "git.hlsq.asia/mmorpg/service-user/internal/dao/repository" + "git.hlsq.asia/mmorpg/service-user/internal/wechat" "gorm.io/gorm" ) -func (s *Server) Login(ctx context.Context, req *grpc_pb.LoginReq) (*grpc_pb.LoginResp, error) { +func (s *Server) PhoneLogin(ctx context.Context, req *grpc_pb.PhoneLoginReq) (*grpc_pb.PhoneLoginResp, error) { userDao := repository.NewUserDao(ctx, redis.GetCacheClient()) user, err := userDao.FindByPhone(req.Phone) if err != nil { @@ -30,7 +31,35 @@ func (s *Server) Login(ctx context.Context, req *grpc_pb.LoginReq) (*grpc_pb.Log } } - return &grpc_pb.LoginResp{ + return &grpc_pb.PhoneLoginResp{ + USN: user.Sn, + Name: user.Name, + }, nil +} + +func (s *Server) WxMiniLogin(ctx context.Context, req *grpc_pb.WxMiniLoginReq) (*grpc_pb.WxMiniLoginResp, error) { + session, err := wechat.MiniCode2Session(req.Code) + if err != nil { + return nil, err + } + userDao := repository.NewUserDao(ctx, redis.GetCacheClient()) + user, err := userDao.FindByWxUnionIDOrOpenID(session.UnionID, session.OpenID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + if user, err = userDao.Create(&model.User{ + WxUnionID: session.UnionID, + WxMiniOpenID: session.OpenID, + }); err != nil { + return nil, err + } + user.Name = fmt.Sprintf("user_%v", user.Sn) + _ = userDao.Updates(user) + } else { + return nil, err + } + } + + return &grpc_pb.WxMiniLoginResp{ USN: user.Sn, Name: user.Name, }, nil diff --git a/internal/wechat/mini.go b/internal/wechat/mini.go new file mode 100644 index 0000000..09fd59b --- /dev/null +++ b/internal/wechat/mini.go @@ -0,0 +1,29 @@ +package wechat + +import ( + "errors" + "git.hlsq.asia/mmorpg/service-common/log" + "git.hlsq.asia/mmorpg/service-user/config" + "net/http" + "net/url" +) + +// MiniCode2Session 根据code获取openID +func MiniCode2Session(code string) (*Code2SessionResp, error) { + values := &url.Values{} + values.Set("appid", config.Get().WxMini.AppID) + values.Set("secret", config.Get().WxMini.Secret) + values.Set("js_code", code) + values.Set("grant_type", "authorization_code") + + resp := &Code2SessionResp{} + err := RequestWechatMini("/sns/jscode2session", http.MethodGet, values, nil, resp) + if err != nil { + return nil, err + } + if resp.ErrCode != ErrorCodeOK { + log.Errorf("[WechatMini] MiniCode2Session err: response.Code = %v, msg: %v, req: %v", resp.ErrCode, resp.ErrMsg, values.Encode()) + return nil, errors.New(resp.ErrMsg) + } + return resp, nil +} diff --git a/internal/wechat/response.go b/internal/wechat/response.go new file mode 100644 index 0000000..be8153a --- /dev/null +++ b/internal/wechat/response.go @@ -0,0 +1,54 @@ +package wechat + +type ResponseBase struct { + ErrCode int32 `json:"errcode"` + ErrMsg string `json:"errmsg"` +} + +type GetAccessTokenResp struct { + ResponseBase + AccessToken string `json:"access_token"` + ExpiresIn int32 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + OpenID string `json:"openid"` + Scope string `json:"scope"` + UnionID string `json:"unionid"` +} + +type GetUserInfoResp struct { + ResponseBase + NickName string `json:"nickname"` + HeadImgURL string `json:"headimgurl"` + UnionID string `json:"unionid"` +} + +type Code2SessionResp struct { + ResponseBase + OpenID string `json:"openid"` + UnionID string `json:"unionid"` +} + +type GetUserPhoneNumberResp struct { + ResponseBase + PhoneInfo *struct { + CountryCode string `json:"countryCode"` + PurePhoneNumber string `json:"purePhoneNumber"` + } `json:"phone_info"` +} + +type GenerateSchemeResp struct { + ResponseBase + OpenLink string `json:"openlink"` +} + +type GenerateURLLinkReq struct { + Path string `json:"path"` // 小程序页面路径 + Query string `json:"query"` // 参数 + ExpireTime int64 `json:"expire_time"` // 失效间隔时间 + EnvVersion string `json:"env_version"` // 版本 +} + +type GenerateURLLinkResp struct { + ResponseBase + UrlLink string `json:"url_link"` +} diff --git a/internal/wechat/service.go b/internal/wechat/service.go new file mode 100644 index 0000000..3389a5d --- /dev/null +++ b/internal/wechat/service.go @@ -0,0 +1,58 @@ +package wechat + +import ( + "bytes" + "encoding/json" + "errors" + "git.hlsq.asia/mmorpg/service-common/log" + "io" + "net/http" + "net/url" + "time" +) + +type ErrorCode int32 + +const ( + ErrorCodeOK = 0 +) + +func RequestWechatMini(path string, method string, params *url.Values, values []byte, response any) error { + u := &url.URL{} + u.Scheme = "https" + u.Host = "api.weixin.qq.com" + u.Path = path + + client := &http.Client{Timeout: 10 * time.Second} + if params != nil { + u.RawQuery = params.Encode() + } + var resp *http.Response + var err error + if method == http.MethodGet { + resp, err = client.Get(u.String()) + } else { + resp, err = client.Post(u.String(), "application/json", bytes.NewBuffer(values)) + } + if err != nil { + log.Errorf("[WechatMini] %v err: %v", path, err) + return err + } + if resp.StatusCode != http.StatusOK { + log.Errorf("[WechatMini] %v err: resp.StatusCode = %v", path, resp.StatusCode) + return errors.New(resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Errorf("[WechatMini] %v err: %v", path, err) + return err + } + err = json.Unmarshal(body, response) + if err != nil { + log.Errorf("[WechatMini] %v err: %v", path, err) + return err + } + + return nil +}