// Package app1svc 是小程序 app1 的业务逻辑。 // 其余小程序(app2..app10)按相同目录结构各自建包,互不耦合。 package app1svc import ( "context" "crypto/rand" "encoding/hex" "time" "portal-mini-go/internal/model" "portal-mini-go/internal/pkg/errors" "portal-mini-go/internal/pkg/session" "portal-mini-go/internal/pkg/wechat" "portal-mini-go/internal/repository/miniapp" "portal-mini-go/internal/repository/user" ) // Service app1 业务服务,依赖用户仓储、小程序仓储、微信客户端与会话存储。 type Service struct { userRepo *user.Repository miniRepo *miniapp.Repository wx *wechat.Client store session.Store } func New(userRepo *user.Repository, miniRepo *miniapp.Repository, wx *wechat.Client, store session.Store) *Service { return &Service{userRepo: userRepo, miniRepo: miniRepo, wx: wx, store: store} } // Login 完成小程序登录:按 app_id 取凭证 -> code2session -> 落库用户 -> 签发会话 token。 // app_id 由接入层(MiniAppAuth 中间件)校验过为已启用小程序,此处用于查出 secret 与小程序主键。 func (s *Service) Login(ctx context.Context, appID, code string) (string, *model.User, error) { mp, err := s.miniRepo.GetActiveByAppID(ctx, appID) if err != nil { return "", nil, err } info, err := s.wx.Code2Session(ctx, appID, mp.AppSecret, code) if err != nil { return "", nil, err } u, err := s.userRepo.UpsertByOpenID(ctx, mp.ID, info.OpenID, info.UnionID, "", "") if err != nil { return "", nil, err } token, err := newSessionToken() if err != nil { return "", nil, err } if err := s.store.Set(ctx, session.MaSessionKey(token), u.ID, 7*24*time.Hour); err != nil { return "", nil, errors.Wrap(50000, "save session", err) } return token, u, nil } // GetProfile 获取当前登录用户资料(绑定到 mini_id,杜绝跨小程序访问)。 func (s *Service) GetProfile(ctx context.Context, miniID, userID uint64) (*model.User, error) { return s.userRepo.GetByID(ctx, miniID, userID) } // UpdateProfile 更新当前登录用户的昵称/头像,为空字段不更新。 func (s *Service) UpdateProfile(ctx context.Context, miniID, userID uint64, nickname, avatar string) error { return s.userRepo.UpdateProfile(ctx, miniID, userID, nickname, avatar) } // GetUser 获取同小程序下指定用户(对外仅暴露非敏感字段,由 handler 控制)。 func (s *Service) GetUser(ctx context.Context, miniID, id uint64) (*model.User, error) { return s.userRepo.GetByID(ctx, miniID, id) } // ListUsers 分页列出同小程序下的用户。 func (s *Service) ListUsers(ctx context.Context, miniID uint64, page, size int) ([]model.User, int64, error) { if page < 1 { page = 1 } if size < 1 || size > 100 { size = 20 } return s.userRepo.List(ctx, miniID, page, size) } // Logout 注销当前用户会话(删除会话 token)。 func (s *Service) Logout(ctx context.Context, token string) error { if err := s.store.Delete(ctx, session.MaSessionKey(token)); err != nil { return errors.Wrap(50000, "delete session", err) } return nil } func newSessionToken() (string, error) { b := make([]byte, 24) if _, err := rand.Read(b); err != nil { return "", errors.Wrap(50000, "gen token", err) } return hex.EncodeToString(b), nil }