Files
iotlcm/server/internal/logic/app/app_user.go
T

627 lines
20 KiB
Go

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:])
}