Files
jx-callback/business/jxstore/cms/user2.go
2020-11-09 11:00:35 +08:00

620 lines
19 KiB
Go

package cms
import (
"errors"
"fmt"
"sync"
"time"
"git.rosy.net.cn/jx-callback/business/jxstore/financial"
"git.rosy.net.cn/baseapi/platformapi/dingdingapi"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/auth2/authprovider/dingding"
"git.rosy.net.cn/jx-callback/business/jxutils"
"git.rosy.net.cn/jx-callback/business/jxutils/jsonerr"
"git.rosy.net.cn/jx-callback/business/jxutils/jxcontext"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
)
var (
ErrUserIDAndNameMustGiven = errors.New("用户ID2和用户名必须不为空")
)
var (
userProvider = &UserProvider{}
authTypeFieldMap = map[string]string{
auth2.UserIDID: "user_id",
auth2.UserIDID2: "user_id2",
auth2.UserIDMobile: "mobile",
auth2.UserIDEmail: "email",
}
jdUsersStruct GetJdUsersStruct
titleListJdUser = []string{
"用户名",
"关联门店",
"所属角色",
"状态",
}
)
type GetJdUsersStruct struct {
locker sync.RWMutex
userMap []JdUserStruct
}
type JdUserStruct struct {
UserName string `json:"用户名"`
StoreIDs string `json:"关联门店"`
RoleName string `json:"所属角色"`
Status string `json:"状态"`
}
type UserProvider struct {
}
func (*UserProvider) GetUser(authID, authIDType string) (user auth2.IUser) {
globals.SugarLogger.Debugf("GetUser, authID:%s, authIDType:%s", authID, authIDType)
fieldName := authTypeFieldMap[authIDType]
if fieldName != "" {
user2, err := dao.GetUserByID(dao.GetDB(), fieldName, authID)
if err == nil {
user = user2 // 这样写的原因是golang nil的比较问题
}
}
return user
}
func (*UserProvider) UpdateUserMobile(userID string, mobile string) (err error) {
_, err = dao.UpdateEntityLogically(dao.GetDB(), &model.User{}, map[string]interface{}{
"Mobile": mobile,
}, userID, map[string]interface{}{
"UserID": userID,
})
return err
}
func (*UserProvider) UpdateUserEmail(userID string, email string) (err error) {
_, err = dao.UpdateEntityLogically(dao.GetDB(), &model.User{}, map[string]interface{}{
"Email": email,
}, userID, map[string]interface{}{
"UserID": userID,
})
return err
}
func (*UserProvider) UpdateUserType(userID string, userTypeMask int8, updateType int) (err error) {
db := dao.GetDB()
user := &model.User{
UserID: userID,
}
if err = dao.GetEntity(db, user, "UserID"); err == nil {
if updateType == auth2.UpdateUserTypeAdd {
user.Type |= userTypeMask
} else if updateType == auth2.UpdateUserTypeDelete {
user.Type &= ^userTypeMask
} else {
user.Type = userTypeMask
}
dao.WrapUpdateULEntity(user, userID)
_, err = dao.UpdateEntity(db, user, "Type", model.FieldUpdatedAt, model.FieldLastOperator)
}
return err
}
func (*UserProvider) UpdateLastLogin(userID string, lastLoginType, fromIP string) (err error) {
_, err = dao.UpdateEntityLogically(dao.GetDB(), &model.User{}, map[string]interface{}{
"LastLoginAt": utils.Time2Pointer(time.Now()),
"LastLoginType": lastLoginType,
"LastLoginIP": fromIP,
}, userID, map[string]interface{}{
"UserID": userID,
})
return err
}
func init() {
auth2.Init(userProvider)
}
func RegisterUserWithMobile(ctx *jxcontext.Context, user *model.User, mobileVerifyCode string, inAuthInfo, manTokenInfo *auth2.AuthInfo) (outAuthInfo *auth2.AuthInfo, err error) {
var mobileAuth *auth2.AuthInfo
fakeMobile := false
user.Type = model.UserTypeConsumer | model.UserTypeStoreBoss // 先不区分商户与消息者
createName := ctx.GetRealRemoteIP()
authType := auth2.AuthTypeMobile
if manTokenInfo != nil && mobileVerifyCode == "" {
manUser, err2 := dao.GetUserByID(dao.GetDB(), "user_id", manTokenInfo.GetID())
if err = err2; err != nil {
return nil, err
}
if manUser.Type&(model.UserTypeOperator|model.UserTypeBoss) == 0 {
return nil, fmt.Errorf("管理员才能添加商户")
}
if utils.Pointer2String(user.Mobile) == "" {
return nil, fmt.Errorf("管理员添加必须指定用户手机号")
}
mobileVerifyCode = auth2.InternalAuthSecret
fakeMobile = true
user.Type |= model.UserTypeStoreBoss
createName = manTokenInfo.GetName()
}
if mobileVerifyCode != "" {
if fakeMobile {
mobileAuth, err = auth2.LoginInternal(ctx.Context, auth2.AuthTypeMobile, user.GetMobile(), auth2.UserIDMobile, mobileVerifyCode)
} else {
mobileAuth, err = auth2.Login(ctx.Context, auth2.AuthTypeMobile, user.GetMobile(), auth2.UserIDMobile, mobileVerifyCode)
}
if err != nil {
return nil, err
}
if mobileAuth != nil && !mobileAuth.IsUserEmpty() {
return nil, jsonerr.New(mobileAuth, model.ErrCodeJsonUserAlreadyExist)
// auth2.RemoveUserInfo(mobileAuth.Token)
// if newAuthInfo, err := auth2.BindUser(mobileAuth, user); err == nil {
// globals.SugarLogger.Debugf("testRegisterUserWithMobile", utils.Format4Output(mobileAuth, false), utils.Format4Output(newAuthInfo, false))
// return nil, jsonerr.New(newAuthInfo, model.ErrCodeJsonUserAlreadyExist)
// } else {
// return nil, err
// }
}
} else if inAuthInfo != nil {
user.Mobile = nil
} else {
return nil, fmt.Errorf("短信验证码与其它认证方式至少要指定一种")
}
if inAuthInfo != nil {
if inAuthInfo.AuthBindInfo.Type == dingding.AuthTypeStaff {
user.Type |= model.UserTypeOperator
} else if user.Mobile != nil {
user.Type |= model.UserTypeStoreBoss
}
createName += "," + inAuthInfo.GetAuthID()
authType = inAuthInfo.GetAuthType()
if user.Avatar == "" {
user.Avatar = inAuthInfo.GetAvatar()
}
}
if err = CreateUser(user, utils.LimitUTF8StringLen(createName, 32)); err == nil {
userProvider.UpdateLastLogin(user.GetID(), authType, ctx.GetRealRemoteIP())
// TryAddStoreBossRole4User(ctx, user)
if mobileAuth != nil {
if outAuthInfo, err = auth2.BindUser(mobileAuth, user); err == nil && inAuthInfo != nil {
err = auth2.AddAuthBind(&outAuthInfo.UserBasic, inAuthInfo)
}
} else {
outAuthInfo, err = auth2.BindUser(inAuthInfo, user)
}
//给用户添加一条账单记录
err = AddUserBill(user)
} else if dao.IsDuplicateError(err) {
err = auth2.ErrUserID2AlreadyExist
}
return outAuthInfo, err
}
func AddUserBill(user *model.User) (err error) {
var (
db = dao.GetDB()
)
userBill := &model.UserBill{
BillID: jxutils.GenBillID(),
UserID: user.UserID,
}
dao.WrapAddIDCULDEntity(userBill, jxcontext.AdminCtx.GetUserName())
return dao.CreateEntity(db, userBill)
}
func GetUserBindAuthInfo(ctx *jxcontext.Context) (authList []*model.AuthBind, err error) {
authInfo, err := ctx.GetV2AuthInfo()
if err == nil {
return auth2.GetUserBindAuthInfo(authInfo.GetID())
}
return nil, err
}
func CreateUser(user *model.User, creatorName string) (err error) {
globals.SugarLogger.Debugf("CreateUser user:%s, creatorName:%s", utils.Format4Output(user, true), creatorName)
if user == nil || user.UserID2 == "" || user.Name == "" {
return ErrUserIDAndNameMustGiven
}
if user.GetMobile() == "" {
user.Mobile = nil
}
if user.GetEmail() == "" {
user.Email = nil
}
dao.WrapAddIDCULDEntity(user, creatorName)
user.UserID = utils.GetUUID()
user.Status = model.UserStatusNormal
// user.DividePercentage = 1
return dao.CreateEntity(nil, user)
}
func DisableUser(ctx *jxcontext.Context, userID string) (err error) {
userName := ctx.GetUserName()
if _, err = dao.UpdateEntityLogically(dao.GetDB(), &model.User{}, map[string]interface{}{
"Status": model.UserStatusDisabled,
}, userName, map[string]interface{}{
"UserID": userID,
}); err == nil {
auth2.DisableUser(userID, userName)
}
return err
}
func OnDingDingMsg(msg map[string]interface{}) (callbackResponse *dingdingapi.CallbackResponse) {
eventType := utils.Interface2String(msg[dingdingapi.KeyEventType])
if eventType == dingdingapi.CBTagUserLeaveOrg {
var (
authBind *model.AuthBind
err error
)
db := dao.GetDB()
for _, userID := range msg[dingdingapi.KeyUserID].([]interface{}) {
userIDStr := utils.Interface2String(userID)
globals.SugarLogger.Debugf("OnDingDingMsg dingding user:%s left company", userIDStr)
if authBind, err = dao.GetAuthBind(db, model.AuthBindTypeAuth, dingding.AuthTypeStaff, userIDStr); err == nil { // 直接找到了
globals.SugarLogger.Debugf("OnDingDingMsg dingding user:%s, userID:%s left company", userIDStr, authBind.UserID)
if err = DisableUser(jxcontext.AdminCtx, authBind.UserID); err != nil {
globals.SugarLogger.Errorf("OnDingDingMsg failed with error:%v", err)
}
}
}
}
return api.DingDingAPI.Err2CallbackResponse(nil)
}
func GetUsers(ctx *jxcontext.Context, userType int, keyword string, userIDs []string, userID2, mobile string, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) {
userList, totalCount, err := dao.GetUsers(dao.GetDB(), userType, keyword, userIDs, jxutils.BatchString2Slice(userID2), jxutils.BatchString2Slice(mobile), offset, pageSize)
if err == nil {
pagedInfo = &model.PagedInfo{
TotalCount: totalCount,
Data: userList,
}
}
return pagedInfo, err
}
func checkUserType(userID string, userType int8) (err error) {
userList, _, err := dao.GetUsers(dao.GetDB(), 0, "", []string{userID}, nil, nil, 0, 0)
if err != nil {
return err
}
if len(userList) == 0 {
return fmt.Errorf("找不到用户:%s", userID)
}
if userList[0].Type&userType == 0 {
return fmt.Errorf("用户:%s不能用于当前操作", userID)
}
return nil
}
func getAddressInfoFromCoord(db *dao.DaoDB, lng, lat float64) (formattedAddress string, districtCode, cityCode int, err error) {
regeoInfo, err := api.AutonaviAPI.GeoCodeRegeoSingle(lng, lat, 0, false, nil, 0, 0)
if err == nil {
formattedAddress = regeoInfo.FormattedAddress
districtCode = int(utils.Str2Int64WithDefault(regeoInfo.AddressComponent.Adcode, 0))
if districtCode == 0 {
err = fmt.Errorf("坐标lng:%f,lat:%f找不到位置信息", lng, lat)
} else if districtInfo, err2 := dao.GetPlaceByCode(db, districtCode); err2 == nil {
cityCode = districtInfo.ParentCode
}
}
return formattedAddress, districtCode, cityCode, err
}
func AddMyDeliveryAddress(ctx *jxcontext.Context, address *model.UserDeliveryAddress) (outAddress *model.UserDeliveryAddress, err error) {
globals.SugarLogger.Debugf("AddMyDeliveryAddress address:%s", utils.Format4Output(address, true))
_, address.UserID = ctx.GetMobileAndUserID()
outAddress, err = AddUserDeliveryAddress(ctx, address)
globals.SugarLogger.Debugf("AddMyDeliveryAddress2 address:%s, err:%v", utils.Format4Output(address, true), err)
return outAddress, err
}
func DeleteUserDeliveryAddress(ctx *jxcontext.Context, userID string, addressID int) (err error) {
num, err := dao.DeleteEntityLogically(dao.GetDB(), &model.UserDeliveryAddress{}, nil, ctx.GetUserName(), map[string]interface{}{
model.FieldID: addressID,
"UserID": userID,
})
if err == nil {
if num == 0 {
err = fmt.Errorf("地址ID:%d不存在", addressID)
}
}
return err
}
func DeleteMyDeliveryAddress(ctx *jxcontext.Context, addressID int) (err error) {
_, userID := ctx.GetMobileAndUserID()
return DeleteUserDeliveryAddress(ctx, userID, addressID)
}
func UpdateUserDeliveryAddress(ctx *jxcontext.Context, userID string, addressID int, payload map[string]interface{}) (err error) {
if userID == "" {
return fmt.Errorf("操作用户配送地址时必须指定UserID")
}
address := &model.UserDeliveryAddress{
UserID: userID,
}
address.ID = addressID
db := dao.GetDB()
if err = dao.GetEntity(db, address, model.FieldID, "UserID"); err == nil {
var outAddress *model.UserDeliveryAddress
valid := dao.StrictMakeMapByStructObject2(payload, address, &outAddress, ctx.GetUserName())
delete(valid, "autoAddress")
delete(valid, "districtCode")
delete(valid, "cityCode")
if len(valid) > 0 {
if valid["lng"] != nil || valid["lat"] != nil {
valid["autoAddress"], valid["districtCode"], valid["cityCode"], err = getAddressInfoFromCoord(db, outAddress.Lng, outAddress.Lat)
if err != nil {
return err
}
}
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
if utils.ForceInterface2Int64(valid["isDefault"]) == 1 {
if err = dao.ClearUserDeliveryAddressDefault(db, userID, 0); err != nil {
dao.Rollback(db)
return err
}
}
if _, err = dao.UpdateEntityLogically(db, address, valid, ctx.GetUserName(), nil); err == nil {
dao.Commit(db)
} else {
dao.Rollback(db)
}
}
}
return err
}
func UpdateMyDeliveryAddress(ctx *jxcontext.Context, addressID int, payload map[string]interface{}) (err error) {
_, userID := ctx.GetMobileAndUserID()
return UpdateUserDeliveryAddress(ctx, userID, addressID, payload)
}
func QueryUserDeliveryAddress(ctx *jxcontext.Context, userIDs []string, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) {
addressList, totalCount, err := dao.QueryUserDeliveryAddress(dao.GetDB(), 0, userIDs, 0, offset, pageSize)
if err == nil {
pagedInfo = &model.PagedInfo{
TotalCount: totalCount,
Data: addressList,
}
}
return pagedInfo, err
}
func QueryMyDeliveryAddress(ctx *jxcontext.Context, addType int) (addressList []*dao.UserDeliveryAddressEx, err error) {
_, userID := ctx.GetMobileAndUserID()
addressList, _, err = dao.QueryUserDeliveryAddress(dao.GetDB(), 0, []string{userID}, addType, 0, model.UnlimitedPageSize)
return addressList, err
}
func AddUserDeliveryAddress(ctx *jxcontext.Context, address *model.UserDeliveryAddress) (outAddress *model.UserDeliveryAddress, err error) {
globals.SugarLogger.Debugf("AddUserDeliveryAddress1 address:%s", utils.Format4Output(address, true))
if address.UserID == "" {
return nil, fmt.Errorf("操作用户配送地址时必须指定UserID")
}
db := dao.GetDB()
lng := address.Lng
lat := address.Lat
address.AutoAddress, address.DistrictCode, address.CityCode, err = getAddressInfoFromCoord(db, lng, lat)
if err == nil {
globals.SugarLogger.Debugf("AddUserDeliveryAddress2 address:%s", utils.Format4Output(address, true))
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
dao.WrapAddIDCULDEntity(address, ctx.GetUserName())
if address.IsDefault == 1 {
if err = dao.ClearUserDeliveryAddressDefault(db, address.UserID, 0); err != nil {
dao.Rollback(db)
return nil, err
}
}
if err = dao.CreateEntity(db, address); err == nil {
dao.Commit(db)
outAddress = address
} else {
dao.Rollback(db)
}
}
globals.SugarLogger.Debugf("AddUserDeliveryAddress3 address:%s", utils.Format4Output(address, true))
return outAddress, err
}
func SaveUserCart(ctx *jxcontext.Context, userID string, storeID int, cartItems []*model.UserCartItem) (err error) {
if userID == "" || storeID == 0 {
return fmt.Errorf("用户与门店必须要指定")
}
for _, v := range cartItems {
v.UserID = userID
v.StoreID = storeID
dao.WrapAddIDCULEntity(v, userID)
}
db := dao.GetDB()
dao.Begin(db)
defer func() {
if r := recover(); r != nil || err != nil {
dao.Rollback(db)
if r != nil {
panic(r)
}
}
}()
_, err = dao.ExecuteSQL(db, `
DELETE t1
FROM user_cart_item t1
WHERE t1.user_id = ? AND t1.store_id = ?
`, userID, storeID)
if err != nil {
return err
}
if len(cartItems) > 0 {
err = dao.CreateMultiEntities(db, cartItems)
}
if err == nil {
dao.Commit(db)
}
return err
}
func LoadUserCart(ctx *jxcontext.Context, userID string, storeIDs []int) (cartItems []*model.UserCartItem, err error) {
if userID == "" || len(storeIDs) == 0 {
return nil, fmt.Errorf("用户与门店必须要指定")
}
sql := `
SELECT t1.*
FROM user_cart_item t1
WHERE t1.user_id = ? AND t1.store_id IN (` + dao.GenQuestionMarks(len(storeIDs)) + `)
ORDER BY t1.sku_id
`
err = dao.GetRows(dao.GetDB(), &cartItems, sql, userID, storeIDs)
return cartItems, err
}
func GetSelfInfo(ctx *jxcontext.Context) (getSelfInfoResult *dao.GetSelfInfoResult, err error) {
tokenInfo, err := auth2.GetTokenInfo(ctx.GetToken())
if err == nil {
if getSelfInfoResult, err = dao.GetUserByIDWithMembers(dao.GetDB(), "user_id", tokenInfo.GetID()); err == nil {
if userMembers, err3 := dao.GetUserMember(dao.GetDB(), getSelfInfoResult.User.UserID, 0); err3 == nil {
getSelfInfoResult.UserMembers = userMembers
} else {
err = err3
}
}
}
return getSelfInfoResult, err
}
func RefreshUserMemberStatus(ctx *jxcontext.Context) (err error) {
var (
db = dao.GetDB()
)
userMembers, err := dao.GetUserMember(db, "", 0)
for _, userMember := range userMembers {
if time.Now().Sub(userMember.EndAt) > 0 {
userMember.DeletedAt = time.Now()
dao.UpdateEntity(db, userMember, "DeletedAt")
}
}
return err
}
func InvestMember(ctx *jxcontext.Context, memberID int) (errCode string, err error) {
var (
db = dao.GetDB()
memberCards []*model.MemberCard
memberCard *model.MemberCard
)
userMembers, err := dao.GetUserMember(db, ctx.GetUserID(), model.MemberTypeNormal)
configList, err := dao.QueryConfigs(db, model.ConfigTypeName[model.ConfigTypeMemberCard], model.ConfigTypeMemberCard, "")
userBill, err := dao.GetUserBill(db, ctx.GetUserID(), "")
if len(configList) <= 0 {
return "", fmt.Errorf("未找到会员卡配置!")
}
config := configList[0]
err = jxutils.Strings2Objs(config.Value, &memberCards)
if err != nil {
return "", err
}
for _, v := range memberCards {
if v.ID == memberID {
memberCard = v
}
}
if userBill.AccountBalance < memberCard.Price {
return model.ErrCodeAccountBalanceNotEnough, fmt.Errorf("用户余额不足,请充值!")
}
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
//证明已经开了会员了,相当于续费
if len(userMembers) > 0 {
userMember := userMembers[0]
if memberID == model.MemberCardTypeMonth {
userMember.EndAt = userMember.EndAt.AddDate(0, 1, 0)
} else {
userMember.EndAt = userMember.EndAt.AddDate(1, 0, 0)
}
if _, err = dao.UpdateEntity(db, userMember, "EndAt"); err != nil {
dao.Rollback(db)
}
} else {
userMember2 := &model.UserMember{
UserID: ctx.GetUserID(),
MemberType: model.MemberTypeNormal,
MemberTypeID: memberID,
}
if memberID == model.MemberCardTypeMonth {
userMember2.EndAt = time.Now().AddDate(0, 1, 0)
} else {
userMember2.EndAt = time.Now().AddDate(1, 0, 0)
}
dao.WrapAddIDCULDEntity(userMember2, ctx.GetUserName())
if err = dao.CreateEntity(db, userMember2); err != nil {
dao.Rollback(db)
}
}
//支出明细
if err = financial.AddBillExpend(db, userBill.BillID, model.BillTypeMember, memberCard.Price); err != nil {
dao.Rollback(db)
}
//账户余额支出
userBill.AccountBalance -= memberCard.Price
if _, err = dao.UpdateEntity(db, userBill, "AccountBalance"); err != nil {
dao.Rollback(db)
}
dao.Commit(db)
return errCode, err
}
func UpdateUser(ctx *jxcontext.Context, payload map[string]interface{}) (err error) {
var (
db = dao.GetDB()
userID = ctx.GetUserID()
)
user := &model.User{
UserID: userID,
}
dao.GetEntity(db, user, "UserID")
valid := dao.StrictMakeMapByStructObject(payload, user, ctx.GetUserName())
if len(valid) > 0 {
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
if _, err = dao.UpdateEntityByKV(db, user, valid, nil); err != nil {
dao.Rollback(db)
return err
}
dao.Commit(db)
}
return err
}