package app import ( "context" "strings" "time" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" ) type sAppUser struct{} func NewAppUser() *sAppUser { return &sAppUser{} } func init() { service.RegisterAppUser(NewAppUser()) } func (s *sAppUser) Info(ctx context.Context) (res *model.AppUserInfoModel, err error) { userId := contexts.GetUserId(ctx) if userId == 0 { return nil, gerror.New("登录已过期") } return getAppUserInfoById(ctx, userId) } func (s *sAppUser) UpdateInfo(ctx context.Context, param *model.AppUpdateUserInfoParam) (res *model.AppUserInfoModel, err error) { userId := contexts.GetUserId(ctx) if userId == 0 { return nil, gerror.New("登录已过期") } if param == nil { return nil, gerror.New("请求参数不能为空") } now := gtime.Now() err = dao.SysUser.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { var user *entity.SysUser if err := dao.SysUser.Ctx(ctx).TX(tx). Where(dao.SysUser.Columns().UserId, userId). Scan(&user); err != nil { return err } if user == nil { return gerror.New("用户不存在") } if !appIsTerminalUser(ctx, user) { return gerror.New("代理商账号仅支持查看资料") } phone := strings.TrimSpace(param.Phone) if phone == "" { phone = strings.TrimSpace(user.Phonenumber) } if phone == "" { return gerror.New("联系电话不能为空") } if phone != "" && phone != strings.TrimSpace(user.Phonenumber) { var exists *entity.SysUser if err := dao.SysUser.Ctx(ctx).TX(tx). Where(dao.SysUser.Columns().TenantId, user.TenantId). Where(dao.SysUser.Columns().Phonenumber, phone). WhereNot(dao.SysUser.Columns().UserId, userId). WhereNull(dao.SysUser.Columns().DeletedAt). Limit(1). Scan(&exists); err != nil { return err } if exists != nil && exists.UserId > 0 { return gerror.New("手机号已被其他账号使用") } } if _, err := dao.SysUser.Ctx(ctx).TX(tx). Where(dao.SysUser.Columns().UserId, userId). Data(do.SysUser{ NickName: param.NickName, Avatar: param.Avatar, Email: strings.TrimSpace(param.Email), Phonenumber: phone, Sex: param.Sex, UpdatedBy: userId, UpdatedAt: now, }). OmitNil(). Update(); err != nil { return err } user.NickName = strings.TrimSpace(param.NickName) user.Email = strings.TrimSpace(param.Email) user.Phonenumber = phone return syncTerminalUserProfile(ctx, tx, user, param, now) }) if err != nil { return nil, err } return getAppUserInfoById(ctx, userId) } func (s *sAppUser) BindWechat(ctx context.Context, param *model.AppWechatBindParam) (res *model.AppWechatBindModel, err error) { if param == nil || strings.TrimSpace(param.WxCode) == "" { return nil, gerror.New("微信登录凭证不能为空") } config, err := appLoadWechatConfig(ctx) if err != nil { return nil, err } if !config.Mini.Enabled { return nil, gerror.New("微信小程序绑定暂未启用") } user, err := appCurrentUser(ctx) if err != nil { return nil, err } if err = service.AppAuth().BindWechatOpenIdByLoginCode(ctx, user, param.WxCode); err != nil { return nil, err } userInfo, err := getAppUserInfoById(ctx, user.UserId) if err != nil { return nil, err } if !userInfo.WechatBound { return nil, gerror.New("微信绑定未完成,请检查小程序配置后重试") } return &model.AppWechatBindModel{ Success: true, UserInfo: userInfo, }, nil } func syncTerminalUserProfile(ctx context.Context, tx gdb.TX, user *entity.SysUser, param *model.AppUpdateUserInfoParam, now *gtime.Time) error { nickName := strings.TrimSpace(param.NickName) terminalName := strings.TrimSpace(appFirstNotEmpty(param.TerminalName, nickName, user.NickName, user.UserName)) phone := strings.TrimSpace(appFirstNotEmpty(param.Phone, user.Phonenumber)) email := strings.TrimSpace(appFirstNotEmpty(param.Email, user.Email)) establishTime, err := appParseProfileDate(param.EstablishTime) if err != nil { return err } var terminal *entity.TTerminal if err = dao.TTerminal.Ctx(ctx).TX(tx). Where(dao.TTerminal.Columns().TerminalId, user.UserId). Scan(&terminal); err != nil { return err } if terminal == nil { if _, err := dao.TTerminal.Ctx(ctx).TX(tx).Data(do.TTerminal{ TerminalId: user.UserId, TerminalName: terminalName, Phone: phone, Email: email, Status: consts.SysUserStatusNormal, CreditCode: strings.TrimSpace(param.CreditCode), AreaCode: strings.TrimSpace(param.AreaCode), Address: strings.TrimSpace(param.Address), FullName: strings.TrimSpace(appFirstNotEmpty(param.FullName, terminalName)), Industry: strings.TrimSpace(param.Industry), LegalPersonName: strings.TrimSpace(appFirstNotEmpty(param.LegalPersonName, terminalName)), LegalPersonIdCard: strings.TrimSpace(appFirstNotEmpty(param.LegalPersonIdCard, "000000000000000000")), CompanyType: strings.TrimSpace(param.CompanyType), EstablishTime: establishTime, BankName: strings.TrimSpace(param.BankName), BankAccount: strings.TrimSpace(param.BankAccount), Balance: 0, TenantId: user.TenantId, CreatedDept: appUserDeptOrDefault(user), CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, }).Insert(); err != nil { return err } } else { if _, err := dao.TTerminal.Ctx(ctx).TX(tx). Where(dao.TTerminal.Columns().TerminalId, user.UserId). Data(do.TTerminal{ TerminalName: terminalName, Phone: phone, Email: email, CreditCode: strings.TrimSpace(param.CreditCode), AreaCode: strings.TrimSpace(param.AreaCode), Address: strings.TrimSpace(param.Address), FullName: strings.TrimSpace(param.FullName), Industry: strings.TrimSpace(param.Industry), LegalPersonName: strings.TrimSpace(param.LegalPersonName), LegalPersonIdCard: strings.TrimSpace(param.LegalPersonIdCard), CompanyType: strings.TrimSpace(param.CompanyType), EstablishTime: establishTime, BankName: strings.TrimSpace(param.BankName), BankAccount: strings.TrimSpace(param.BankAccount), UpdatedBy: user.UserId, UpdatedAt: now, }). OmitNil(). Update(); err != nil { return err } } if _, err := dao.TSimTerminal.Ctx(ctx).TX(tx). Where(dao.TSimTerminal.Columns().TenantId, user.TenantId). Where(dao.TSimTerminal.Columns().TerminalId, user.UserId). Data(do.TSimTerminal{ TerminalName: terminalName, UpdatedBy: user.UserId, UpdatedAt: now, }). OmitNil(). Update(); err != nil { return err } if _, err := dao.TPackageTerminal.Ctx(ctx).TX(tx). Where(dao.TPackageTerminal.Columns().TenantId, user.TenantId). Where(dao.TPackageTerminal.Columns().TerminalId, user.UserId). Data(do.TPackageTerminal{ TerminalName: terminalName, UpdatedBy: user.UserId, UpdatedAt: now, }). OmitNil(). Update(); err != nil { return err } return nil } func appParseProfileDate(value string) (*gtime.Time, error) { value = strings.TrimSpace(value) if value == "" { return nil, nil } parsed, err := time.Parse("2006-01-02", value) if err != nil { return nil, gerror.New("成立时间格式应为YYYY-MM-DD") } return gtime.NewFromTime(parsed), nil } func getAppUserInfoById(ctx context.Context, userId int64) (res *model.AppUserInfoModel, err error) { var user *entity.SysUser err = dao.SysUser.Ctx(ctx). Where(dao.SysUser.Columns().UserId, userId). Scan(&user) if err != nil { return nil, err } if user == nil { return nil, gerror.New("用户不存在") } isTerminal := appIsTerminalUser(ctx, user) canEnterpriseBusiness := !isTerminal && user.DeptId > 0 balance := float64(0) role := "企业用户" phoneText := appMaskPhone(user.Phonenumber) var enterpriseProfile *model.AppEnterpriseProfileModel var terminalProfile *model.AppTerminalProfileModel if isTerminal { role = "终端用户" phoneText = strings.TrimSpace(user.Phonenumber) var terminal *entity.TTerminal if err = dao.TTerminal.Ctx(ctx). Where(dao.TTerminal.Columns().TenantId, user.TenantId). Where(dao.TTerminal.Columns().TerminalId, user.UserId). Limit(1). Scan(&terminal); err != nil { return nil, err } if terminal != nil { balance = terminal.Balance terminalProfile = appTerminalProfileModel(terminal) } } else if user.DeptId > 0 { var agentBalance *entity.TAgentBalance if err = dao.TAgentBalance.Ctx(ctx). Where(dao.TAgentBalance.Columns().TenantId, user.TenantId). Where(dao.TAgentBalance.Columns().DeptId, user.DeptId). Scan(&agentBalance); err != nil { return nil, err } if agentBalance != nil { balance = agentBalance.Balance } enterpriseProfile, err = appEnterpriseProfileByDept(ctx, user) if err != nil { return nil, err } } roleName, roleKey, roleNames := appLoadBoundRoleNames(ctx, user.UserId) isTopLevelEnterprise, err := appIsTopLevelEnterprise(ctx, user) if err != nil { return nil, err } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } return &model.AppUserInfoModel{ IsTopLevelEnterprise: isTopLevelEnterprise, UserId: user.UserId, TenantId: user.TenantId, DeptId: user.DeptId, Username: user.UserName, NickName: user.NickName, Phone: phoneText, Avatar: user.Avatar, Sex: user.Sex, UserType: user.UserType, IsTerminal: isTerminal, CanEnterpriseBusiness: canEnterpriseBusiness, WechatBound: wechatBound, WechatBindText: wechatBindText, Balance: balance, Role: role, RoleName: roleName, RoleKey: roleKey, RoleNames: roleNames, EnterpriseProfile: enterpriseProfile, TerminalProfile: terminalProfile, }, nil } // 顶级企业按机构层级识别,不依赖运营、管理员等角色名称。 func appIsTopLevelEnterprise(ctx context.Context, user *entity.SysUser) (bool, error) { if user == nil || user.UserId <= 0 || user.UserType == consts.SysUserTypeAPP || appIsTerminalUser(ctx, user) { return false, nil } if user.UserId == 1 { return true, nil } if user.DeptId <= 0 { return false, nil } var dept *entity.SysDept err := dao.SysDept.Ctx(ctx).Fields("dept_id", "parent_id", "status", "deleted_at").Where("tenant_id", user.TenantId).Where("dept_id", user.DeptId).Scan(&dept) if err != nil { return false, gerror.Wrap(err, "查询诊断企业层级失败") } return dept != nil && dept.ParentId == 0 && dept.Status == "0" && (dept.DeletedAt == nil || dept.DeletedAt.IsZero()), nil } // appLoadBoundRoleNames 读取账户在 sys_user_role 中绑定的角色,并关联 sys_role 返回真实角色名称。 // 这样既保留原有账号类型 role,又能让前端按真实的角色表名称展示账户身份标识。 func appLoadBoundRoleNames(ctx context.Context, userId int64) (roleName string, roleKey string, roleNames []string) { roleNames = make([]string, 0) var userRoles []*entity.SysUserRole if err := dao.SysUserRole.Ctx(ctx). Where(dao.SysUserRole.Columns().UserId, userId). Scan(&userRoles); err != nil || len(userRoles) == 0 { return "", "", roleNames } roleIds := make([]int64, 0, len(userRoles)) for _, ur := range userRoles { if ur != nil && ur.RoleId > 0 { roleIds = append(roleIds, ur.RoleId) } } if len(roleIds) == 0 { return "", "", roleNames } var roles []*entity.SysRole if err := dao.SysRole.Ctx(ctx). WhereIn(dao.SysRole.Columns().RoleId, roleIds). Scan(&roles); err != nil || len(roles) == 0 { return "", "", roleNames } for _, r := range roles { if r == nil { continue } if name := strings.TrimSpace(r.RoleName); name != "" { roleNames = append(roleNames, name) } } if roles[0] != nil { roleName = strings.TrimSpace(roles[0].RoleName) roleKey = strings.TrimSpace(roles[0].RoleKey) } return roleName, roleKey, roleNames } func appUserWechatBindingStatus(ctx context.Context, user *entity.SysUser) (bool, string, error) { if user == nil || user.UserId <= 0 { return false, "未绑定", nil } var social *entity.SysSocial err := dao.SysSocial.Ctx(ctx). Fields(dao.SysSocial.Columns().Id). Where(dao.SysSocial.Columns().TenantId, user.TenantId). Where(dao.SysSocial.Columns().UserId, user.UserId). Where(dao.SysSocial.Columns().Source, consts.SourceWechatMp). Where(dao.SysSocial.Columns().OpenId+" IS NOT NULL"). Where(dao.SysSocial.Columns().OpenId+" <> ?", ""). WhereNull(dao.SysSocial.Columns().DeletedAt). OrderDesc(dao.SysSocial.Columns().Id). Limit(1). Scan(&social) if err != nil { return false, "", gerror.Wrap(err, "查询微信绑定状态失败") } if social == nil || social.Id == 0 { return false, "未绑定", nil } return true, "已绑定", nil } func appEnterpriseProfileByDept(ctx context.Context, user *entity.SysUser) (*model.AppEnterpriseProfileModel, error) { if user == nil || user.DeptId <= 0 { return nil, nil } var dept *entity.SysDept if err := dao.SysDept.Ctx(ctx). Where(dao.SysDept.Columns().TenantId, user.TenantId). Where(dao.SysDept.Columns().DeptId, user.DeptId). Limit(1). Scan(&dept); err != nil { return nil, gerror.Wrap(err, "查询企业基础信息失败") } if dept == nil || dept.DeptId == 0 { return nil, nil } var enterprise *entity.TAgentEnterprise if err := dao.TAgentEnterprise.Ctx(ctx). Where(dao.TAgentEnterprise.Columns().TenantId, user.TenantId). Where(dao.TAgentEnterprise.Columns().DeptId, user.DeptId). Limit(1). Scan(&enterprise); err != nil { return nil, gerror.Wrap(err, "查询企业客户档案失败") } profile := &model.AppEnterpriseProfileModel{ DeptId: dept.DeptId, DeptName: strings.TrimSpace(dept.DeptName), DeptKey: strings.TrimSpace(dept.DeptKey), Phone: appMaskPhone(dept.Phone), Email: appMaskEmail(dept.Email), StatusText: appDeptStatusText(dept.Status), CreditCode: appMaskCreditCode(dept.CreditCode), AreaCode: strings.TrimSpace(dept.AreaCode), Address: appMaskAddress(dept.Address), FullName: strings.TrimSpace(dept.DeptName), RealNameStatus: "实名资料待完善", } if enterprise != nil && enterprise.Id > 0 { profile.FullName = appFirstNotEmpty(enterprise.FullName, dept.DeptName) profile.Industry = strings.TrimSpace(enterprise.Industry) profile.LegalPersonName = appMaskName(enterprise.LegalPersonName) profile.LegalPersonIdCard = appMaskIdCard(enterprise.LegalPersonIdCard) profile.CompanyType = strings.TrimSpace(enterprise.CompanyType) profile.EstablishTime = appProfileDate(enterprise.EstablishTime) profile.BankName = strings.TrimSpace(enterprise.BankName) profile.BankAccount = appMaskBankAccount(enterprise.BankAccount) profile.RealNameStatus = appRealNameStatus(enterprise.FullName, enterprise.LegalPersonName, enterprise.LegalPersonIdCard) } return profile, nil } func appTerminalProfileModel(terminal *entity.TTerminal) *model.AppTerminalProfileModel { if terminal == nil || terminal.TerminalId == 0 { return nil } return &model.AppTerminalProfileModel{ TerminalId: terminal.TerminalId, TerminalName: strings.TrimSpace(terminal.TerminalName), Phone: strings.TrimSpace(terminal.Phone), Email: strings.TrimSpace(terminal.Email), Status: strings.TrimSpace(terminal.Status), StatusText: appTerminalStatusText(terminal.Status), CreditCode: strings.TrimSpace(terminal.CreditCode), AreaCode: strings.TrimSpace(terminal.AreaCode), Address: strings.TrimSpace(terminal.Address), FullName: strings.TrimSpace(terminal.FullName), Industry: strings.TrimSpace(terminal.Industry), LegalPersonName: strings.TrimSpace(terminal.LegalPersonName), LegalPersonIdCard: strings.TrimSpace(terminal.LegalPersonIdCard), CompanyType: strings.TrimSpace(terminal.CompanyType), EstablishTime: appProfileDate(terminal.EstablishTime), BankName: strings.TrimSpace(terminal.BankName), BankAccount: strings.TrimSpace(terminal.BankAccount), Balance: appRoundAmount(terminal.Balance), RealNameStatus: appRealNameStatus(terminal.FullName, terminal.LegalPersonName, terminal.LegalPersonIdCard), } } func appProfileDate(value *gtime.Time) string { if value == nil || value.IsZero() { return "" } return value.Format("Y-m-d") } func appTerminalStatusText(status string) string { switch strings.TrimSpace(status) { case consts.SysUserStatusNormal: return "正常" case "1": return "停用" case "": return "未设置" default: return "未知" } } func appRealNameStatus(fullName string, legalPersonName string, legalPersonIdCard string) string { if strings.TrimSpace(fullName) == "" && strings.TrimSpace(legalPersonName) == "" { return "资料待完善" } idCard := strings.TrimSpace(legalPersonIdCard) if idCard == "" || idCard == "000000000000000000" { return "实名资料待完善" } return "已录入实名资料" } func appMaskIdCard(value string) string { value = strings.TrimSpace(value) if value == "" || value == "000000000000000000" { return "" } runes := []rune(value) if len(runes) <= 8 { return value } return string(runes[:4]) + strings.Repeat("*", len(runes)-8) + string(runes[len(runes)-4:]) } func appMaskBankAccount(value string) string { value = strings.TrimSpace(value) if value == "" { return "" } runes := []rune(value) if len(runes) <= 8 { return value } return string(runes[:4]) + strings.Repeat("*", len(runes)-8) + string(runes[len(runes)-4:]) } func appMaskPhone(value string) string { value = strings.TrimSpace(value) runes := []rune(value) if len(runes) < 7 { return value } return string(runes[:3]) + "****" + string(runes[len(runes)-4:]) } func appMaskEmail(value string) string { value = strings.TrimSpace(value) if value == "" { return "" } parts := strings.SplitN(value, "@", 2) if len(parts) != 2 { return appMaskMiddle(value, 1, 1) } name := []rune(parts[0]) if len(name) <= 1 { return "*" + "@" + parts[1] } return string(name[:1]) + strings.Repeat("*", len(name)-1) + "@" + parts[1] } func appMaskCreditCode(value string) string { return appMaskMiddle(value, 4, 4) } func appMaskAddress(value string) string { value = strings.TrimSpace(value) if value == "" { return "" } runes := []rune(value) if len(runes) <= 6 { return strings.Repeat("*", len(runes)) } return string(runes[:6]) + strings.Repeat("*", len(runes)-6) } func appMaskName(value string) string { value = strings.TrimSpace(value) if value == "" { return "" } runes := []rune(value) if len(runes) <= 1 { return "*" } return string(runes[:1]) + strings.Repeat("*", len(runes)-1) } func appMaskMiddle(value string, prefix int, suffix int) string { value = strings.TrimSpace(value) if value == "" { return "" } runes := []rune(value) if len(runes) <= prefix+suffix { if len(runes) <= 2 { return strings.Repeat("*", len(runes)) } return string(runes[:1]) + strings.Repeat("*", len(runes)-2) + string(runes[len(runes)-1:]) } return string(runes[:prefix]) + strings.Repeat("*", len(runes)-prefix-suffix) + string(runes[len(runes)-suffix:]) }