From e027c03950710e4d6d5dda7542a200f6e71f0463 Mon Sep 17 00:00:00 2001 From: wucongxing <815046773@qq.com> Date: Fri, 7 Jul 2023 17:58:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BF=AE=E6=94=B9=E5=8C=BB?= =?UTF-8?q?=E7=94=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controller/userDoctor.go | 2 +- api/dao/adminMenu.go | 9 + api/dao/user.go | 101 ++++++++ api/dao/userCaCert.go | 71 ++++++ api/model/userCaCert.go | 20 ++ api/service/role.go | 17 +- api/service/userDoctor.go | 168 ++++++++++++-- config.yaml | 9 +- config/config.go | 3 +- config/im.go | 8 + extend/ca/ca.go | 82 ++++++- extend/ca/caOnline.go | 324 +++++++++++++++++++++++++- extend/tencentIm/TLSSigAPI.go | 422 ++++++++++++++++++++++++++++++++++ extend/tencentIm/base.go | 94 ++++++++ extend/tencentIm/profile.go | 53 +++++ go.mod | 1 + go.sum | 2 + 17 files changed, 1345 insertions(+), 41 deletions(-) create mode 100644 api/dao/user.go create mode 100644 api/dao/userCaCert.go create mode 100644 api/model/userCaCert.go create mode 100644 config/im.go create mode 100644 extend/tencentIm/TLSSigAPI.go create mode 100644 extend/tencentIm/base.go create mode 100644 extend/tencentIm/profile.go diff --git a/api/controller/userDoctor.go b/api/controller/userDoctor.go index e6fb966..10eaceb 100644 --- a/api/controller/userDoctor.go +++ b/api/controller/userDoctor.go @@ -115,7 +115,7 @@ func (r *UserDoctor) PutUserDoctor(c *gin.Context) { userDoctorService := service.UserDoctorService{} _, err = userDoctorService.PutUserDoctor(doctorId, userDoctorRequest.PutUserDoctor) if err != nil { - responses.FailWithMessage("修改失败", c) + responses.FailWithMessage(err.Error(), c) return } diff --git a/api/dao/adminMenu.go b/api/dao/adminMenu.go index 5e3af33..843820b 100644 --- a/api/dao/adminMenu.go +++ b/api/dao/adminMenu.go @@ -98,3 +98,12 @@ func (r *AdminMenuDao) GetAdminMenuId() (m []int64, err error) { } return m, nil } + +// GetAdminMenuListByIdWithIn 获取全部正常菜单列表-in +func (r *AdminMenuDao) GetAdminMenuListByIdWithIn(menuIds []int64) (m []*model.AdminMenu, err error) { + err = global.Db.Where("menu_id in ?", menuIds).Where("menu_status = ?", 1).Order("order_num asc").Find(&m).Error + if err != nil { + return nil, err + } + return m, nil +} diff --git a/api/dao/user.go b/api/dao/user.go new file mode 100644 index 0000000..3ee5b3a --- /dev/null +++ b/api/dao/user.go @@ -0,0 +1,101 @@ +package dao + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" + "hospital-admin-api/api/model" + "hospital-admin-api/api/requests" + "hospital-admin-api/global" +) + +type UserDao struct { +} + +// GetUserById 获取用户数据-用户id +func (r *UserDao) GetUserById(userId int64) (m *model.User, err error) { + err = global.Db.First(&m, userId).Error + if err != nil { + return nil, err + } + return m, nil +} + +// GetUserPreloadById 获取用户数据-加载全部关联-用户id +func (r *UserDao) GetUserPreloadById(userId int64) (m *model.User, err error) { + err = global.Db.Preload(clause.Associations).First(&m, userId).Error + if err != nil { + return nil, err + } + return m, nil +} + +// DeleteUser 删除用户 +func (r *UserDao) DeleteUser(tx *gorm.DB, maps interface{}) error { + err := tx.Where(maps).Delete(&model.User{}).Error + if err != nil { + return err + } + return nil +} + +// DeleteUserById 删除用户-用户id +func (r *UserDao) DeleteUserById(tx *gorm.DB, userId int64) error { + if err := tx.Delete(&model.User{}, userId).Error; err != nil { + return err + } + return nil +} + +// EditUser 修改用户 +func (r *UserDao) EditUser(tx *gorm.DB, maps interface{}, data interface{}) error { + err := tx.Model(&model.User{}).Where(maps).Updates(data).Error + if err != nil { + return err + } + return nil +} + +// EditUserById 修改用户-用户id +func (r *UserDao) EditUserById(tx *gorm.DB, userId int64, data interface{}) error { + err := tx.Model(&model.User{}).Where("user_id = ?", userId).Updates(data).Error + if err != nil { + return err + } + return nil +} + +// GetUserList 获取用户列表 +func (r *UserDao) GetUserList(maps interface{}) (m []*model.User, err error) { + err = global.Db.Where(maps).Find(&m).Error + if err != nil { + return nil, err + } + return m, nil +} + +// GetUserDaoPageSearch 获取用户列表-分页 +func (r *UserDao) GetUserDaoPageSearch(getUserPage requests.GetUserPage, page, pageSize int) (m []*model.User, total int64, err error) { + var totalRecords int64 + + // 构建查询条件 + query := global.Db.Model(&model.User{}) + + // 查询总数量 + if err := query.Count(&totalRecords).Error; err != nil { + return nil, 0, err + } + + err = query.Scopes(model.Paginate(page, pageSize)).Find(&m).Error + if err != nil { + return nil, 0, err + } + return m, totalRecords, nil +} + +// AddUser 新增用户 +func (r *UserDao) AddUser(tx *gorm.DB, model *model.User) (*model.User, error) { + if err := tx.Create(model).Error; err != nil { + return nil, err + } + return model, nil +} diff --git a/api/dao/userCaCert.go b/api/dao/userCaCert.go new file mode 100644 index 0000000..bb12513 --- /dev/null +++ b/api/dao/userCaCert.go @@ -0,0 +1,71 @@ +package dao + +import ( + "gorm.io/gorm" + "hospital-admin-api/api/model" + "hospital-admin-api/global" +) + +type UserCaCert struct { +} + +// GetUserCaCertById 获取监管证书数据-监管证书id +func (r *UserCaCert) GetUserCaCertById(certId int64) (m *model.UserCaCert, err error) { + err = global.Db.First(&m, certId).Error + if err != nil { + return nil, err + } + return m, nil +} + +// GetUserCaCertByUserId 获取监管证书数据-监管证书id +func (r *UserCaCert) GetUserCaCertByUserId(userId int64) (m *model.UserCaCert, err error) { + err = global.Db.Where("user_id = ?", userId).First(&m).Error + if err != nil { + return nil, err + } + return m, nil +} + +// GetUserCaCertListByUserId 获取监管证书数据-监管证书id +func (r *UserCaCert) GetUserCaCertListByUserId(userId int64) (m []*model.UserCaCert, err error) { + err = global.Db.Where("user_id = ?", userId).Find(&m).Error + if err != nil { + return nil, err + } + return m, nil +} + +// AddUserCaCert 新增监管证书 +func (r *UserCaCert) AddUserCaCert(tx *gorm.DB, model *model.UserCaCert) (*model.UserCaCert, error) { + if err := tx.Create(model).Error; err != nil { + return nil, err + } + return model, nil +} + +// GetUserCaCertList 获取监管证书列表 +func (r *UserCaCert) GetUserCaCertList(maps interface{}) (m []*model.UserCaCert, err error) { + err = global.Db.Where(maps).Find(&m).Error + if err != nil { + return nil, err + } + return m, nil +} + +// DeleteUserCaCertById 删除监管证书-监管证书id +func (r *UserCaCert) DeleteUserCaCertById(tx *gorm.DB, certId int64) error { + if err := tx.Delete(&model.UserCaCert{}, certId).Error; err != nil { + return err + } + return nil +} + +// EditUserCaCertById 修改监管证书-监管证书id +func (r *UserCaCert) EditUserCaCertById(tx *gorm.DB, certId int64, data interface{}) error { + err := tx.Model(&model.UserCaCert{}).Where("cert_id = ?", certId).Updates(data).Error + if err != nil { + return err + } + return nil +} diff --git a/api/model/userCaCert.go b/api/model/userCaCert.go new file mode 100644 index 0000000..e2672f4 --- /dev/null +++ b/api/model/userCaCert.go @@ -0,0 +1,20 @@ +package model + +// UserCaCert 医师/药师ca监管证书表 +type UserCaCert struct { + CertId int64 `gorm:"column:cert_id;type:bigint(19);primary_key;comment:主键id" json:"cert_id"` + UserId int64 `gorm:"column:user_id;type:bigint(19);comment:用户id(系统证书时为null)" json:"user_id"` + IsSystem int `gorm:"column:is_system;type:tinyint(1);default:0;comment:是否系统证书(0:否 1:是)" json:"is_system"` + Type int `gorm:"column:type;type:tinyint(1);comment:证书类型(1:线下 2:线上)" json:"type"` + CertBase64 string `gorm:"column:cert_base64;type:text;comment:签名值证书" json:"cert_base64"` + CertChainP7 string `gorm:"column:cert_chain_p7;type:text;comment:证书链" json:"cert_chain_p7"` + CertSerialNumber string `gorm:"column:cert_serial_number;type:varchar(100);comment:证书序列号" json:"cert_serial_number"` + CaPin string `gorm:"column:ca_pin;type:varchar(100);comment:ca认证pin值" json:"ca_pin"` + IsSignConfig int `gorm:"column:is_sign_config;type:tinyint(1);default:0;comment:是否已添加签章配置(第一次需申请)" json:"is_sign_config"` + SignConfig string `gorm:"column:sign_config;type:text;comment:签章坐标配置" json:"sign_config"` + Model +} + +func (m *UserCaCert) TableName() string { + return "gdxz_user_ca_cert" +} diff --git a/api/service/role.go b/api/service/role.go index f5683ef..662d310 100644 --- a/api/service/role.go +++ b/api/service/role.go @@ -16,28 +16,29 @@ type RoleService struct{} // GetRoleMenuList 获取角色菜单 func (r *RoleService) GetRoleMenuList(roleId int64, isAdmin bool) ([]*roleResponse.GetRoleMenuList, error) { // 获取角色菜单 - AdminRoleMenuDao := dao.AdminRoleMenuDao{} - AdminMenuDao := dao.AdminMenuDao{} + adminRoleMenuDao := dao.AdminRoleMenuDao{} + adminMenuDao := dao.AdminMenuDao{} var menuIDs []int64 var err error if isAdmin { // 超级管理员 - menuIDs, err = AdminMenuDao.GetAdminMenuId() + menuIDs, err = adminMenuDao.GetAdminMenuId() if err != nil { - return []*roleResponse.GetRoleMenuList{}, nil + return nil, nil } } else { - menuIDs, err = AdminRoleMenuDao.GetAdminRoleMenuIdByRoleId(roleId) + // 普通用户 + menuIDs, err = adminRoleMenuDao.GetAdminRoleMenuIdByRoleId(roleId) if err != nil { - return []*roleResponse.GetRoleMenuList{}, nil + return nil, nil } } // 获取全部正常非按钮菜单列表 - adminMenu, _ := AdminMenuDao.GetAdminMenuListNonButtonNormalSortOrderNum() + adminMenu, _ := adminMenuDao.GetAdminMenuListNonButtonNormalSortOrderNum() if adminMenu == nil { - return []*roleResponse.GetRoleMenuList{}, nil + return nil, nil } var getRoleMenuListResponse []*roleResponse.GetRoleMenuList diff --git a/api/service/userDoctor.go b/api/service/userDoctor.go index 09b7c19..cda0b2e 100644 --- a/api/service/userDoctor.go +++ b/api/service/userDoctor.go @@ -5,6 +5,8 @@ import ( "hospital-admin-api/api/dao" "hospital-admin-api/api/requests" "hospital-admin-api/config" + "hospital-admin-api/extend/ca" + "hospital-admin-api/extend/tencentIm" "hospital-admin-api/global" "strconv" "strings" @@ -15,6 +17,7 @@ type UserDoctorService struct { // PutUserDoctor 修改医生 func (r *UserDoctorService) PutUserDoctor(doctorId int64, userDoctorRequest requests.PutUserDoctor) (bool, error) { + // 获取医生数据 userDoctorDao := dao.UserDoctorDao{} userDoctor, err := userDoctorDao.GetUserDoctorById(doctorId) @@ -29,17 +32,22 @@ func (r *UserDoctorService) PutUserDoctor(doctorId int64, userDoctorRequest requ return false, errors.New("医生详情数据错误") } - // 医生数据 - userDoctorData := make(map[string]interface{}) + // 获取用户数据 + userDao := dao.UserDao{} + user, err := userDao.GetUserById(userDoctor.UserId) + if err != nil { + return false, errors.New("医生数据错误") + } - // 医生详情数据 - userDoctorInfoData := make(map[string]interface{}) + userDoctorData := make(map[string]interface{}) // 医生数据 + userDoctorInfoData := make(map[string]interface{}) // 医生详情数据 + userData := make(map[string]interface{}) // 用户数据 // 处理头像 - userDoctorRequest.Avatar = strings.Replace(userDoctorRequest.Avatar, config.C.Oss.OssCustomDomainName, "", 1) - if userDoctor.Avatar != userDoctorRequest.Avatar { - userDoctorData["avatar"] = userDoctorRequest.Avatar - userDoctorInfoData["avatar"] = userDoctorRequest.Avatar + avatar := strings.Replace(userDoctorRequest.Avatar, config.C.Oss.OssCustomDomainName, "", 1) + if userDoctor.Avatar != avatar { + userDoctorData["avatar"] = avatar + userData["avatar"] = avatar } // 处理职称 @@ -53,6 +61,9 @@ func (r *UserDoctorService) PutUserDoctor(doctorId int64, userDoctorRequest requ return false, errors.New("科室错误") } + if departmentCustomId == 0 && userDoctor.DepartmentCustomId != 0 { + return false, errors.New("未选择新的科室") + } if userDoctor.DepartmentCustomId != departmentCustomId || userDoctor.DepartmentCustomName != userDoctorRequest.DepartmentCustomName { // 获取科室数据 hospitalDepartmentCustomDao := dao.HospitalDepartmentCustom{} @@ -237,25 +248,132 @@ func (r *UserDoctorService) PutUserDoctor(doctorId int64, userDoctorRequest requ } } - // 判断头像是否修改,同步修改im - // 判断科室是否修改,同步修改ca平台 - // 判断签名图片是否修改,同步修改ca平台 + // 修改医生数据 + if len(userDoctorData) != 0 { + err = userDao.EditUserById(tx, userDoctor.UserId, userData) + if err != nil { + tx.Rollback() + return false, errors.New("修改失败") + } + } - // _, ok := userDoctorData["department_custom_id"] - // if ok { - // // 变更科室 - // - // } - // - // paramMap := map[string]string{ - // "userName": "zj", - // "age": "19", - // } - // - // sign := ca.GenerateSignature(paramMap) - // fmt.Println(sign) + // 判断头像是否修改,同步修改im + if userDoctor.Avatar != avatar { + profileItem := []tencentIm.ProfileItem{ + { + Tag: "Tag_Profile_IM_Image", + Value: userDoctorRequest.Avatar, + }, + } + res, err := tencentIm.SetProfile(strconv.FormatInt(userDoctor.UserId, 10), profileItem) + if err != nil || res != true { + tx.Rollback() + return false, errors.New("头像设置失败") + } + } + + // 判断科室是否修改,同步修改ca平台 + if userDoctor.DepartmentCustomId != departmentCustomId { + // 获取科室数据 + hospitalDepartmentCustomDao := dao.HospitalDepartmentCustom{} + hospitalDepartmentCustom, err := hospitalDepartmentCustomDao.GetHospitalDepartmentCustomById(departmentCustomId) + if err != nil || hospitalDepartmentCustom == nil { + tx.Rollback() + return false, errors.New("科室错误") + } + + // 获取云证书数据 + userCaCertDao := dao.UserCaCert{} + userCaCerts, err := userCaCertDao.GetUserCaCertListByUserId(userDoctor.UserId) + if err != nil { + tx.Rollback() + return false, errors.New("修改失败") + } + + if userCaCerts != nil && len(userCaCerts) > 0 { + // 修改云证书 + editCloudCertRequestData := &ca.EditCloudCertRequestData{ + EntityId: strconv.FormatInt(userDoctor.UserId, 10), + EntityType: "Personal", + PersonalPhone: user.Mobile, + PersonalName: userDoctorInfo.CardName, + PersonalIdNumber: userDoctorInfo.CardNum, + OrgName: "", + OrgNumber: "", + Pin: strconv.FormatInt(userDoctor.UserId, 10), + OrgDept: hospitalDepartmentCustom.DepartmentName, // // 卫生证书:医院部门 + Province: "四川省", + Locality: "成都市", + AuthType: "实人认证", + // AuthTime: strconv.FormatInt(time.Now().Unix(), 10), + AuthTime: "1688694270", + AuthResult: "认证通过", + AuthNoticeType: "数字证书变更告知", + } + + editCloudCertResponse, err := ca.EditCloudCert(editCloudCertRequestData) + if err != nil || editCloudCertResponse == nil { + tx.Rollback() + return false, errors.New(err.Error()) + } + + // 修改ca监管证书表 + userCaCertDao := dao.UserCaCert{} + data := make(map[string]interface{}) + data["cert_base64"] = editCloudCertResponse.CertBase64 + data["cert_chain_p7"] = editCloudCertResponse.CertP7 + data["cert_serial_number"] = editCloudCertResponse.CertP7 + err = userCaCertDao.EditUserCaCertById(tx, userCaCerts[0].CertId, data) + if err != nil { + tx.Rollback() + return false, errors.New("删除失败") + } + } + } + + // 判断签名图片是否修改,同步修改ca平台 + // 1、新用户,未存在证书 + if userDoctorRequest.SignImage != "" { + signImage := strings.Replace(userDoctorRequest.SignImage, "https://img.applets.igandanyiyuan.com", "", 1) + if signImage != userDoctorInfo.SignImage { + // 检测是否存在云证书 + userCaCertDao := dao.UserCaCert{} + userCaCerts, err := userCaCertDao.GetUserCaCertListByUserId(userDoctor.UserId) + if err != nil { + tx.Rollback() + return false, errors.New("修改失败") + } + + if userCaCerts != nil && len(userCaCerts) > 0 { + userCaCert := userCaCerts[0] + // 检测是否已经添加签章配置 + if userCaCert.IsSignConfig == 1 { + // 修改签章配置为未添加 + data := make(map[string]interface{}) + data["is_sign_config"] = 0 + err = userCaCertDao.EditUserCaCertById(tx, userCaCert.CertId, data) + if err != nil { + tx.Rollback() + return false, errors.New(err.Error()) + } + + // 删除签章配置 + deleteUserSignConfigRequestData := &ca.DeleteUserSignConfigRequestData{ + UserId: strconv.FormatInt(userDoctor.UserId, 10), + ConfigKey: strconv.FormatInt(userDoctor.UserId, 10), + } + + _, err := ca.DeleteUserSignConfig(deleteUserSignConfigRequestData) + if err != nil { + tx.Rollback() + return false, errors.New(err.Error()) + } + } + } + + } + } tx.Commit() return true, nil - // } diff --git a/config.yaml b/config.yaml index af2a909..ffe4bc7 100644 --- a/config.yaml +++ b/config.yaml @@ -44,4 +44,11 @@ oss: ca-online: ca-online-app-id: SCCA1646691325903052802 ca-online-app-secret: adf718ebc1fb4bb7b158de9117d1313a - ca-online-api-url: http://testmicrosrv.scca.com.cn:9527 \ No newline at end of file + ca-online-api-url: http://testmicrosrv.scca.com.cn:9527 + +# [腾讯im] +im: + im-app-id: 1400798221 + im-secret: fc45ab469ca632a700166973d87b3a6f56a855cb92d7cffb54e4d37135c097da + im-base-url: https://console.tim.qq.com/ + im-token: NDc5MzExMDMxMDY2NDMxNDg5L \ No newline at end of file diff --git a/config/config.go b/config/config.go index 76c6710..ab19a80 100644 --- a/config/config.go +++ b/config/config.go @@ -11,5 +11,6 @@ type Config struct { Jwt Jwt `mapstructure:"jwt" json:"jwt" yaml:"jwt"` Oss Oss `mapstructure:"oss" json:"oss" yaml:"oss"` Snowflake int64 `mapstructure:"snowflake" json:"snowflake" yaml:"snowflake"` - CaOnline CaOnline `mapstructure:"ca_online" json:"ca_online" yaml:"ca_online"` + CaOnline CaOnline `mapstructure:"ca-online" json:"ca-online" yaml:"ca-online"` + Im Im `mapstructure:"im" json:"im" yaml:"im"` } diff --git a/config/im.go b/config/im.go new file mode 100644 index 0000000..a4738d5 --- /dev/null +++ b/config/im.go @@ -0,0 +1,8 @@ +package config + +type Im struct { + ImAppID int `mapstructure:"im-app-id" json:"im-app-id" yaml:"im-app-id"` + ImSecret string `mapstructure:"im-secret" json:"im-secret" yaml:"im-secret"` + ImBaseUrl string `mapstructure:"im-base-url" json:"im-base-url" yaml:"im-base-url"` + ImToken string `mapstructure:"im-token" json:"im-token" yaml:"im-token"` +} diff --git a/extend/ca/ca.go b/extend/ca/ca.go index 4a4ebd5..fc44851 100644 --- a/extend/ca/ca.go +++ b/extend/ca/ca.go @@ -4,13 +4,25 @@ import ( "crypto/hmac" "crypto/sha1" "encoding/hex" + "encoding/json" + "errors" "hospital-admin-api/config" + "io" + "net/http" + "net/url" "sort" "strings" ) +type Response struct { + ResultCode int `json:"result_code"` + ResultMsg string `json:"result_msg"` + Body interface{} `json:"body"` + Success bool `json:"success"` +} + // GenerateSignature 生成签名 -func GenerateSignature(paramMap map[string]string) string { +func GenerateSignature(paramMap map[string]interface{}) string { keys := make([]string, 0, len(paramMap)) for k := range paramMap { if k == "pdfFile" { @@ -22,7 +34,10 @@ func GenerateSignature(paramMap map[string]string) string { var toSign string for _, k := range keys { - v := paramMap[k] + v, ok := paramMap[k].(string) + if !ok { + return "" + } toSign += v + "&" } toSign = strings.TrimSuffix(toSign, "&") @@ -34,3 +49,66 @@ func GenerateSignature(paramMap map[string]string) string { return signature } + +// 统一请求 +func postRequest(requestUrl string, formData url.Values, signature string) (map[string]interface{}, error) { + payload := strings.NewReader(formData.Encode()) + // 创建 POST 请求 + req, err := http.NewRequest("POST", requestUrl, payload) + if err != nil { + return nil, err + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("app_id", config.C.CaOnline.CaOnlineAppId) + req.Header.Add("signature", signature) + + // 创建 HTTP 请求客户端 + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + if resp.StatusCode != 200 { + return nil, errors.New("请求失败") + } + // 读取响应内容 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var response Response + err = json.Unmarshal([]byte(respBody), &response) + if err != nil { + // json解析失败 + return nil, err + } + + if response.ResultCode != 0 { + if response.ResultMsg != "" { + return nil, errors.New(response.ResultMsg) + } else { + return nil, errors.New("请求ca失败") + } + } + + body := make(map[string]interface{}) + if response.Body != nil || response.Body != "" { + bodyMap, ok := response.Body.(map[string]interface{}) + if !ok { + body = bodyMap + } + } + + if len(body) == 0 { + return nil, nil + } + return body, nil +} diff --git a/extend/ca/caOnline.go b/extend/ca/caOnline.go index 7c2d40b..e9f0994 100644 --- a/extend/ca/caOnline.go +++ b/extend/ca/caOnline.go @@ -1,5 +1,323 @@ package ca -// func GenerateSi1gnature(paramMap map[string]string, secret string) string { -// -// } +import ( + "errors" + "hospital-admin-api/config" + "net/url" +) + +// EditCloudCertRequestData 修改云证书请求数据 +type EditCloudCertRequestData struct { + EntityId string `json:"entityId"` // 用户唯一标识,由业务系统定义 + EntityType string `json:"entityType"` // 用户类型,可选值[Personal/Organizational] + PersonalPhone string `json:"personalPhone"` // 联系人电话 + PersonalName string `json:"personalName"` // 个人姓名,类型为Personal时必填 + PersonalIdNumber string `json:"personalIdNumber"` // 个人证件号,类型为Personal时必填 + OrgName string `json:"orgName"` // 组织机构名称,信用代码类型为Organizational时必填 + OrgNumber string `json:"orgNumber"` // 组织机构代码,信用代码类型为Organizational时必填 + Pin string `json:"pin"` // 证书PIN码 + OrgDept string `json:"orgDept"` // 卫生证书:医院部门 + Province string `json:"province"` // 卫生证书:省、州 + Locality string `json:"locality"` // 卫生证书:城市 + AuthType string `json:"authType"` // 委托鉴证方式[实人认证、线下认证、其它方式认证] + AuthTime string `json:"authTime"` // 委托鉴证时间(鉴证完成的时间戳)单位:秒 + AuthResult string `json:"authResult"` // 委托鉴证结果[认证通过] + AuthNoticeType string `json:"authNoticeType"` // 委托鉴证告知类型[数字证书申请告知] +} + +// AddCloudCertRequestData 新增云证书请求数据 +type AddCloudCertRequestData struct { + EntityId string `json:"entityId"` // 用户唯一标识,由业务系统定义 + EntityType string `json:"entityType"` // 用户类型,可选值[Personal/Organizational] + PersonalPhone string `json:"personalPhone"` // 联系人电话 + PersonalName string `json:"personalName"` // 个人姓名,类型为Personal时必填 + PersonalIdNumber string `json:"personalIdNumber"` // 个人证件号,类型为Personal时必填 + OrgName string `json:"orgName"` // 组织机构名称,信用代码类型为Organizational时必填 + OrgNumber string `json:"orgNumber"` // 组织机构代码,信用代码类型为Organizational时必填 + Pin string `json:"pin"` // 证书PIN码 + OrgDept string `json:"orgDept"` // 卫生证书:医院部门 + Province string `json:"province"` // 卫生证书:省、州 + Locality string `json:"locality"` // 卫生证书:城市 + AuthType string `json:"authType"` // 委托鉴证方式[实人认证、线下认证、其它方式认证] + AuthTime string `json:"authTime"` // 委托鉴证时间(鉴证完成的时间戳)单位:秒 + AuthResult string `json:"authResult"` // 委托鉴证结果[认证通过] + AuthNoticeType string `json:"authNoticeType"` // 委托鉴证告知类型[数字证书申请告知] +} + +// GetUserSignConfigRequestData 获取用户签章图片 +type GetUserSignConfigRequestData struct { + UserId string `json:"userId"` // 用户标识信息 +} + +// DeleteUserSignConfigRequestData 删除签章配置 +type DeleteUserSignConfigRequestData struct { + UserId string `json:"userId"` // 用户标识信息 + ConfigKey string `json:"configKey"` // 签章配置唯一标识 +} + +// EditCloudCertResponse 修改云证书返回数据 +type EditCloudCertResponse struct { + CertBase64 string `json:"certBase64"` // 签名值证书 + CertP7 string `json:"certP7"` // 证书链 + CertSerialnumber string `json:"certSerialnumber"` // 证书序列号 +} + +// AddCloudCertResponse 申请云证书返回数据 +type AddCloudCertResponse struct { + CertBase64 string `json:"certBase64"` // 签名值证书 + CertP7 string `json:"certP7"` // 证书链 + CertSerialnumber string `json:"certSerialnumber"` // 证书序列号 +} + +// GetUserSignConfigResponse 获取用户签章图片返回数据 +type GetUserSignConfigResponse struct { + SealImg string `json:"sealImg"` // 印章图片 + SealType int `json:"sealType"` // 印章类型(1公章;2财务章;3个人章;4合同印章;5其他) + AppId string `json:"appId"` // 应用appid + Id string `json:"id"` // 印章唯一标识 +} + +// EditCloudCert 修改云证书 +func EditCloudCert(d *EditCloudCertRequestData) (*EditCloudCertResponse, error) { + if d == nil { + return nil, errors.New("修改云证书失败") + } + + // 获取签名 + requestDataMap := make(map[string]interface{}) + requestDataMap["entityId"] = d.EntityId + requestDataMap["entityType"] = d.EntityType + requestDataMap["personalPhone"] = d.PersonalPhone + requestDataMap["personalName"] = d.PersonalName + requestDataMap["personalIdNumber"] = d.PersonalIdNumber + requestDataMap["orgName"] = d.OrgName + requestDataMap["orgNumber"] = d.OrgNumber + requestDataMap["pin"] = d.Pin + requestDataMap["province"] = d.Province + requestDataMap["locality"] = d.Locality + requestDataMap["authType"] = d.AuthType + requestDataMap["authTime"] = d.AuthTime + requestDataMap["authResult"] = d.AuthResult + requestDataMap["authNoticeType"] = d.AuthNoticeType + + signature := GenerateSignature(requestDataMap) + if signature == "" { + return nil, errors.New("云证书签名错误") + } + + formData := url.Values{} + formData.Set("entityId", d.EntityId) + formData.Set("entityType", d.EntityType) + formData.Set("personalPhone", d.PersonalPhone) + formData.Set("personalName", d.PersonalName) + formData.Set("personalIdNumber", d.PersonalIdNumber) + formData.Set("orgName", d.OrgName) + formData.Set("orgNumber", d.OrgNumber) + formData.Set("pin", d.Pin) + formData.Set("province", d.Province) + formData.Set("locality", d.Locality) + formData.Set("authType", d.AuthType) + formData.Set("authTime", d.AuthTime) + formData.Set("authResult", d.AuthResult) + formData.Set("authNoticeType", d.AuthNoticeType) + + // 构建请求 URL + requestUrl := config.C.CaOnline.CaOnlineApiUrl + "/cloud-certificate-service/api/cloudCert/open/v2/cert/certChange" + + response, err := postRequest(requestUrl, formData, signature) + if err != nil { + return nil, errors.New(err.Error()) + } + + certBase64, ok := response["certBase64"] + if !ok { + return nil, errors.New("返回数据错误") + } + + certP7, ok := response["certP7"] + if !ok { + return nil, errors.New("返回数据错误") + } + + certSerialnumber, ok := response["certSerialnumber"] + if !ok { + return nil, errors.New("返回数据错误") + } + + result := &EditCloudCertResponse{ + CertBase64: certBase64.(string), + CertP7: certP7.(string), + CertSerialnumber: certSerialnumber.(string), + } + return result, nil +} + +// AddCloudCert 新增云证书 +func AddCloudCert(d *AddCloudCertRequestData) (*AddCloudCertResponse, error) { + if d == nil { + return nil, errors.New("修改云证书失败") + } + + // 获取签名 + requestDataMap := make(map[string]interface{}) + requestDataMap["entityId"] = d.EntityId + requestDataMap["entityType"] = d.EntityType + requestDataMap["personalPhone"] = d.PersonalPhone + requestDataMap["personalName"] = d.PersonalName + requestDataMap["personalIdNumber"] = d.PersonalIdNumber + requestDataMap["orgName"] = d.OrgName + requestDataMap["orgNumber"] = d.OrgNumber + requestDataMap["pin"] = d.Pin + requestDataMap["province"] = d.Province + requestDataMap["locality"] = d.Locality + requestDataMap["authType"] = d.AuthType + requestDataMap["authTime"] = d.AuthTime + requestDataMap["authResult"] = d.AuthResult + requestDataMap["authNoticeType"] = d.AuthNoticeType + + signature := GenerateSignature(requestDataMap) + if signature == "" { + return nil, errors.New("云证书签名错误") + } + + formData := url.Values{} + formData.Set("entityId", d.EntityId) + formData.Set("entityType", d.EntityType) + formData.Set("personalPhone", d.PersonalPhone) + formData.Set("personalName", d.PersonalName) + formData.Set("personalIdNumber", d.PersonalIdNumber) + formData.Set("orgName", d.OrgName) + formData.Set("orgNumber", d.OrgNumber) + formData.Set("pin", d.Pin) + formData.Set("province", d.Province) + formData.Set("locality", d.Locality) + formData.Set("authType", d.AuthType) + formData.Set("authTime", d.AuthTime) + formData.Set("authResult", d.AuthResult) + formData.Set("authNoticeType", d.AuthNoticeType) + + // 构建请求 URL + requestUrl := config.C.CaOnline.CaOnlineApiUrl + "/cloud-certificate-service/api/cloudCert/open/v2/cert/certEnroll" + + response, err := postRequest(requestUrl, formData, signature) + if err != nil { + return nil, errors.New(err.Error()) + } + + certBase64, ok := response["certBase64"] + if !ok { + return nil, errors.New("返回数据错误") + } + + certP7, ok := response["certP7"] + if !ok { + return nil, errors.New("返回数据错误") + } + + certSerialnumber, ok := response["certSerialnumber"] + if !ok { + return nil, errors.New("返回数据错误") + } + + result := &AddCloudCertResponse{ + CertBase64: certBase64.(string), + CertP7: certP7.(string), + CertSerialnumber: certSerialnumber.(string), + } + return result, nil +} + +// GetUserSignConfig 获取用户签章图片 +func GetUserSignConfig(d *GetUserSignConfigRequestData) (*GetUserSignConfigResponse, error) { + if d == nil { + return nil, errors.New("修改云证书失败") + } + + // 获取签名 + requestDataMap := make(map[string]interface{}) + requestDataMap["userId"] = d.UserId + + signature := GenerateSignature(requestDataMap) + if signature == "" { + return nil, errors.New("云证书签名错误") + } + + formData := url.Values{} + formData.Set("userId", d.UserId) + + // 构建请求 URL + requestUrl := config.C.CaOnline.CaOnlineApiUrl + "/signature-server/api/open/signature/fetchUserSeal" + + response, err := postRequest(requestUrl, formData, signature) + if err != nil { + return nil, errors.New(err.Error()) + } + + // 返回内容为空,未设置签章图片 + if response == nil { + return nil, nil + } + + sealImg, ok := response["sealImg"] + if !ok { + return nil, errors.New("返回数据错误") + } + + sealType, ok := response["sealType"] + if !ok { + return nil, errors.New("返回数据错误") + } + + appId, ok := response["appId"] + if !ok { + return nil, errors.New("返回数据错误") + } + + id, ok := response["id"] + if !ok { + return nil, errors.New("返回数据错误") + } + + result := &GetUserSignConfigResponse{ + SealImg: sealImg.(string), + SealType: sealType.(int), + AppId: appId.(string), + Id: id.(string), + } + return result, nil +} + +// DeleteUserSignConfig 删除签章配置 +func DeleteUserSignConfig(d *DeleteUserSignConfigRequestData) (bool, error) { + if d == nil { + return false, errors.New("修改云证书失败") + } + + // 获取签名 + requestDataMap := make(map[string]interface{}) + requestDataMap["userId"] = d.UserId + + signature := GenerateSignature(requestDataMap) + if signature == "" { + return false, errors.New("云证书签名错误") + } + + formData := url.Values{} + formData.Set("userId", d.UserId) + formData.Set("configKey", d.ConfigKey) + + // 构建请求 URL + requestUrl := config.C.CaOnline.CaOnlineApiUrl + "/signature-server/api/open/signature/delSignConfig" + + response, err := postRequest(requestUrl, formData, signature) + if err != nil { + return false, errors.New(err.Error()) + } + + // 返回内容为空 + if response == nil { + return true, nil + } + + return true, nil +} diff --git a/extend/tencentIm/TLSSigAPI.go b/extend/tencentIm/TLSSigAPI.go new file mode 100644 index 0000000..6c5c0f3 --- /dev/null +++ b/extend/tencentIm/TLSSigAPI.go @@ -0,0 +1,422 @@ +package tencentIm + +import ( + "bytes" + "compress/zlib" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "strconv" + "strings" + "time" +) + +/** + *【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据 + * + *【参数说明】 + * sdkappid - 应用id + * key - 计算 usersig 用的加密密钥,控制台可获取 + * userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。 + * expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。 + */ + +// GenUserSig /** +func GenUserSig(sdkappid int, key string, userid string, expire int) (string, error) { + return genSig(sdkappid, key, userid, expire, nil) +} + +func GenUserSigWithBuf(sdkappid int, key string, userid string, expire int, buf []byte) (string, error) { + return genSig(sdkappid, key, userid, expire, buf) +} + +/** + *【功能说明】 + * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。 + * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力: + * - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。 + * - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。 + * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。 + * + *【参数说明】 + * sdkappid - 应用id。 + * key - 计算 usersig 用的加密密钥,控制台可获取。 + * userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。 + * expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。 + * roomid - 房间号,用于指定该 userid 可以进入的房间号 + * privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关: + * - 第 1 位:0000 0001 = 1,创建房间的权限 + * - 第 2 位:0000 0010 = 2,加入房间的权限 + * - 第 3 位:0000 0100 = 4,发送语音的权限 + * - 第 4 位:0000 1000 = 8,接收语音的权限 + * - 第 5 位:0001 0000 = 16,发送视频的权限 + * - 第 6 位:0010 0000 = 32,接收视频的权限 + * - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限 + * - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限 + * - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。 + * - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。 + */ + +/** + * Function: + * Used to issue PrivateMapKey that is optional for room entry. + * PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities. + * - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room. + * - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room. + * To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info. + * + * Parameter description: + * sdkappid - Application ID + * userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-). + * key - The encryption key used to calculate usersig can be obtained from the console. + * roomid - ID of the room to which the specified UserID can enter. + * expire - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated. + * privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features: + * - Bit 1: 0000 0001 = 1, permission for room creation + * - Bit 2: 0000 0010 = 2, permission for room entry + * - Bit 3: 0000 0100 = 4, permission for audio sending + * - Bit 4: 0000 1000 = 8, permission for audio receiving + * - Bit 5: 0001 0000 = 16, permission for video sending + * - Bit 6: 0010 0000 = 32, permission for video receiving + * - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing) + * - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing) + * - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid. + * - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data. + */ + +func GenPrivateMapKey(sdkappid int, key string, userid string, expire int, roomid uint32, privilegeMap uint32) (string, error) { + var userbuf []byte = genUserBuf(userid, sdkappid, roomid, expire, privilegeMap, 0, "") + return genSig(sdkappid, key, userid, expire, userbuf) +} + +/** + *【功能说明】 + * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。 + * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力: + * - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。 + * - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。 + * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。 + * + *【参数说明】 + * sdkappid - 应用id。 + * key - 计算 usersig 用的加密密钥,控制台可获取。 + * userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。 + * expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。 + * roomStr - 字符串房间号,用于指定该 userid 可以进入的房间号 + * privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关: + * - 第 1 位:0000 0001 = 1,创建房间的权限 + * - 第 2 位:0000 0010 = 2,加入房间的权限 + * - 第 3 位:0000 0100 = 4,发送语音的权限 + * - 第 4 位:0000 1000 = 8,接收语音的权限 + * - 第 5 位:0001 0000 = 16,发送视频的权限 + * - 第 6 位:0010 0000 = 32,接收视频的权限 + * - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限 + * - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限 + * - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。 + * - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。 + */ + +/** + * Function: + * Used to issue PrivateMapKey that is optional for room entry. + * PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities. + * - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room. + * - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room. + * To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info. + * + * Parameter description: + * sdkappid - Application ID + * userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-). + * key - The encryption key used to calculate usersig can be obtained from the console. + * roomstr - ID of the room to which the specified UserID can enter. + * expire - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated. + * privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features: + * - Bit 1: 0000 0001 = 1, permission for room creation + * - Bit 2: 0000 0010 = 2, permission for room entry + * - Bit 3: 0000 0100 = 4, permission for audio sending + * - Bit 4: 0000 1000 = 8, permission for audio receiving + * - Bit 5: 0001 0000 = 16, permission for video sending + * - Bit 6: 0010 0000 = 32, permission for video receiving + * - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing) + * - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing) + * - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid. + * - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data. + */ +func GenPrivateMapKeyWithStringRoomID(sdkappid int, key string, userid string, expire int, roomStr string, privilegeMap uint32) (string, error) { + var userbuf []byte = genUserBuf(userid, sdkappid, 0, expire, privilegeMap, 0, roomStr) + return genSig(sdkappid, key, userid, expire, userbuf) +} + +func genUserBuf(account string, dwSdkappid int, dwAuthID uint32, + dwExpTime int, dwPrivilegeMap uint32, dwAccountType uint32, roomStr string) []byte { + + offset := 0 + length := 1 + 2 + len(account) + 20 + len(roomStr) + if len(roomStr) > 0 { + length = length + 2 + } + + userBuf := make([]byte, length) + + // ver + if len(roomStr) > 0 { + userBuf[offset] = 1 + } else { + userBuf[offset] = 0 + } + + offset++ + userBuf[offset] = (byte)((len(account) & 0xFF00) >> 8) + offset++ + userBuf[offset] = (byte)(len(account) & 0x00FF) + offset++ + + for ; offset < len(account)+3; offset++ { + userBuf[offset] = account[offset-3] + } + + // dwSdkAppid + userBuf[offset] = (byte)((dwSdkappid & 0xFF000000) >> 24) + offset++ + userBuf[offset] = (byte)((dwSdkappid & 0x00FF0000) >> 16) + offset++ + userBuf[offset] = (byte)((dwSdkappid & 0x0000FF00) >> 8) + offset++ + userBuf[offset] = (byte)(dwSdkappid & 0x000000FF) + offset++ + + // dwAuthId + userBuf[offset] = (byte)((dwAuthID & 0xFF000000) >> 24) + offset++ + userBuf[offset] = (byte)((dwAuthID & 0x00FF0000) >> 16) + offset++ + userBuf[offset] = (byte)((dwAuthID & 0x0000FF00) >> 8) + offset++ + userBuf[offset] = (byte)(dwAuthID & 0x000000FF) + offset++ + + // dwExpTime now+300; + currTime := time.Now().Unix() + var expire = currTime + int64(dwExpTime) + userBuf[offset] = (byte)((expire & 0xFF000000) >> 24) + offset++ + userBuf[offset] = (byte)((expire & 0x00FF0000) >> 16) + offset++ + userBuf[offset] = (byte)((expire & 0x0000FF00) >> 8) + offset++ + userBuf[offset] = (byte)(expire & 0x000000FF) + offset++ + + // dwPrivilegeMap + userBuf[offset] = (byte)((dwPrivilegeMap & 0xFF000000) >> 24) + offset++ + userBuf[offset] = (byte)((dwPrivilegeMap & 0x00FF0000) >> 16) + offset++ + userBuf[offset] = (byte)((dwPrivilegeMap & 0x0000FF00) >> 8) + offset++ + userBuf[offset] = (byte)(dwPrivilegeMap & 0x000000FF) + offset++ + + // dwAccountType + userBuf[offset] = (byte)((dwAccountType & 0xFF000000) >> 24) + offset++ + userBuf[offset] = (byte)((dwAccountType & 0x00FF0000) >> 16) + offset++ + userBuf[offset] = (byte)((dwAccountType & 0x0000FF00) >> 8) + offset++ + userBuf[offset] = (byte)(dwAccountType & 0x000000FF) + offset++ + + if len(roomStr) > 0 { + userBuf[offset] = (byte)((len(roomStr) & 0xFF00) >> 8) + offset++ + userBuf[offset] = (byte)(len(roomStr) & 0x00FF) + offset++ + + for ; offset < length; offset++ { + userBuf[offset] = roomStr[offset-(length-len(roomStr))] + } + } + + return userBuf +} + +func hmacsha256(sdkappid int, key string, identifier string, currTime int64, expire int, base64UserBuf *string) string { + var contentToBeSigned string + contentToBeSigned = "TLS.identifier:" + identifier + "\n" + contentToBeSigned += "TLS.sdkappid:" + strconv.Itoa(sdkappid) + "\n" + contentToBeSigned += "TLS.time:" + strconv.FormatInt(currTime, 10) + "\n" + contentToBeSigned += "TLS.expire:" + strconv.Itoa(expire) + "\n" + if nil != base64UserBuf { + contentToBeSigned += "TLS.userbuf:" + *base64UserBuf + "\n" + } + + h := hmac.New(sha256.New, []byte(key)) + h.Write([]byte(contentToBeSigned)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func genSig(sdkappid int, key string, identifier string, expire int, userbuf []byte) (string, error) { + currTime := time.Now().Unix() + sigDoc := make(map[string]interface{}) + sigDoc["TLS.ver"] = "2.0" + sigDoc["TLS.identifier"] = identifier + sigDoc["TLS.sdkappid"] = sdkappid + sigDoc["TLS.expire"] = expire + sigDoc["TLS.time"] = currTime + var base64UserBuf string + if nil != userbuf { + base64UserBuf = base64.StdEncoding.EncodeToString(userbuf) + sigDoc["TLS.userbuf"] = base64UserBuf + sigDoc["TLS.sig"] = hmacsha256(sdkappid, key, identifier, currTime, expire, &base64UserBuf) + } else { + sigDoc["TLS.sig"] = hmacsha256(sdkappid, key, identifier, currTime, expire, nil) + } + + data, err := json.Marshal(sigDoc) + if err != nil { + return "", err + } + + var b bytes.Buffer + w := zlib.NewWriter(&b) + if _, err = w.Write(data); err != nil { + return "", err + } + if err = w.Close(); err != nil { + return "", err + } + return base64urlEncode(b.Bytes()), nil +} + +// VerifyUserSig 检验UserSig在now时间点时是否有效 +// VerifyUserSig Check if UserSig is valid at now time +func VerifyUserSig(sdkappid uint64, key string, userid string, usersig string, now time.Time) error { + sig, err := newUserSig(usersig) + if err != nil { + return err + } + return sig.verify(sdkappid, key, userid, now, nil) +} + +// VerifyUserSigWithBuf 检验带UserBuf的UserSig在now时间点是否有效 +// VerifyUserSigWithBuf Check if UserSig with UserBuf is valid at now +func VerifyUserSigWithBuf(sdkappid uint64, key string, userid string, usersig string, now time.Time, userbuf []byte) error { + sig, err := newUserSig(usersig) + if err != nil { + return err + } + return sig.verify(sdkappid, key, userid, now, userbuf) +} + +type userSig struct { + Version string `json:"TLS.ver,omitempty"` + Identifier string `json:"TLS.identifier,omitempty"` + SdkAppID uint64 `json:"TLS.sdkappid,omitempty"` + Expire int64 `json:"TLS.expire,omitempty"` + Time int64 `json:"TLS.time,omitempty"` + UserBuf []byte `json:"TLS.userbuf,omitempty"` + Sig string `json:"TLS.sig,omitempty"` +} + +func newUserSig(usersig string) (userSig, error) { + b, err := base64urlDecode(usersig) + if err != nil { + return userSig{}, err + } + r, err := zlib.NewReader(bytes.NewReader(b)) + if err != nil { + return userSig{}, err + } + data, err := ioutil.ReadAll(r) + if err != nil { + return userSig{}, err + } + if err = r.Close(); err != nil { + return userSig{}, err + } + var sig userSig + if err = json.Unmarshal(data, &sig); err != nil { + return userSig{}, nil + } + return sig, nil +} + +func (u userSig) verify(sdkappid uint64, key string, userid string, now time.Time, userbuf []byte) error { + if sdkappid != u.SdkAppID { + return ErrSdkAppIDNotMatch + } + if userid != u.Identifier { + return ErrIdentifierNotMatch + } + if now.Unix() > u.Time+u.Expire { + return ErrExpired + } + if userbuf != nil { + if u.UserBuf == nil { + return ErrUserBufTypeNotMatch + } + if !bytes.Equal(userbuf, u.UserBuf) { + return ErrUserBufNotMatch + } + } else if u.UserBuf != nil { + return ErrUserBufTypeNotMatch + } + if u.sign(key) != u.Sig { + return ErrSigNotMatch + } + return nil +} + +func (u userSig) sign(key string) string { + var sb bytes.Buffer + sb.WriteString("TLS.identifier:") + sb.WriteString(u.Identifier) + sb.WriteString("\n") + sb.WriteString("TLS.sdkappid:") + sb.WriteString(strconv.FormatUint(u.SdkAppID, 10)) + sb.WriteString("\n") + sb.WriteString("TLS.time:") + sb.WriteString(strconv.FormatInt(u.Time, 10)) + sb.WriteString("\n") + sb.WriteString("TLS.expire:") + sb.WriteString(strconv.FormatInt(u.Expire, 10)) + sb.WriteString("\n") + if u.UserBuf != nil { + sb.WriteString("TLS.userbuf:") + sb.WriteString(base64.StdEncoding.EncodeToString(u.UserBuf)) + sb.WriteString("\n") + } + + h := hmac.New(sha256.New, []byte(key)) + h.Write(sb.Bytes()) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func base64urlEncode(data []byte) string { + str := base64.StdEncoding.EncodeToString(data) + str = strings.Replace(str, "+", "*", -1) + str = strings.Replace(str, "/", "-", -1) + str = strings.Replace(str, "=", "_", -1) + return str +} + +func base64urlDecode(str string) ([]byte, error) { + str = strings.Replace(str, "_", "=", -1) + str = strings.Replace(str, "-", "/", -1) + str = strings.Replace(str, "*", "+", -1) + return base64.StdEncoding.DecodeString(str) +} + +// 错误类型 +var ( + ErrSdkAppIDNotMatch = errors.New("sdk appid not match") + ErrIdentifierNotMatch = errors.New("identifier not match") + ErrExpired = errors.New("expired") + ErrUserBufTypeNotMatch = errors.New("userbuf type not match") + ErrUserBufNotMatch = errors.New("userbuf not match") + ErrSigNotMatch = errors.New("sig not match") +) diff --git a/extend/tencentIm/base.go b/extend/tencentIm/base.go new file mode 100644 index 0000000..8b47ba8 --- /dev/null +++ b/extend/tencentIm/base.go @@ -0,0 +1,94 @@ +package tencentIm + +import ( + "bytes" + "encoding/json" + "errors" + "hospital-admin-api/config" + "io" + "math/rand" + "net/http" + "net/url" + "strconv" + "time" +) + +// GetUserSign 获取签名 +func GetUserSign(userId string) (string, error) { + if userId == "" { + userId = "administrator" + } + ImAppID := config.C.Im.ImAppID + ImSecret := config.C.Im.ImSecret + sign, err := GenUserSig(ImAppID, ImSecret, userId, 86400*180) + if err != nil || sign == "" { + return "", errors.New("签名获取失败") + } + + return sign, err +} + +// 获取请求链接 +func getRequestUrlParams(userId string) (bool, string) { + // 获取签名 + // 获取签名 + sign, err := GetUserSign(userId) + if err != nil { + return false, err.Error() + } + + rand.Seed(time.Now().UnixNano()) + + params := url.Values{} + params.Set("sdkappid", strconv.Itoa(config.C.Im.ImAppID)) + params.Set("identifier", "administrator") + params.Set("usersig", sign) + params.Set("random", strconv.Itoa(rand.Intn(4294967296))) + params.Set("contenttype", "json") + + queryString := params.Encode() + + return true, queryString +} + +// 统一请求 +func postRequest(url string, requestBody []byte) (map[string]interface{}, error) { + responseMap := make(map[string]interface{}) + + // 发起 POST 请求 + resp, err := http.Post(url, "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + return nil, err + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(string(body)), &responseMap) + if err != nil { + // json解析失败 + return nil, err + } + + if responseMap == nil { + return nil, errors.New("请求im失败") + } + + if errorCode, ok := responseMap["ErrorCode"]; ok { + if errorCode != 0 { + if errorInfo, ok := responseMap["ErrorInfo"].(string); ok { + return nil, errors.New(errorInfo) + } else { + return nil, errors.New("请求im失败") + } + } + } + + return responseMap, nil +} diff --git a/extend/tencentIm/profile.go b/extend/tencentIm/profile.go new file mode 100644 index 0000000..9bf97f8 --- /dev/null +++ b/extend/tencentIm/profile.go @@ -0,0 +1,53 @@ +// Package tencentIm im资料 +package tencentIm + +import ( + "encoding/json" + "errors" + "hospital-admin-api/config" +) + +// ProfileItem 资料对象数组 +type ProfileItem struct { + Tag string `json:"Tag"` + Value string `json:"Value"` +} + +// PortraitSetRequest 请求格式 +type PortraitSetRequest struct { + FromAccount string `json:"From_Account"` + ProfileItems []ProfileItem `json:"ProfileItem"` +} + +// SetProfile 设置账户资料 +func SetProfile(userId string, profileItem []ProfileItem) (bool, error) { + if len(profileItem) == 0 { + return false, errors.New("未设置资料") + } + + // 构建请求数据 + requestData := &PortraitSetRequest{ + FromAccount: userId, + ProfileItems: profileItem, + } + + // 将请求数据转换为 JSON + requestBody, err := json.Marshal(requestData) + if err != nil { + return false, errors.New("设置im资料失败") + } + + // 构建请求 URL + res, result := getRequestUrlParams("administrator") + if res != true { + return false, errors.New(result) + } + url := config.C.Im.ImBaseUrl + "v4/profile/portrait_set?" + result + + _, err = postRequest(url, requestBody) + if err != nil { + return false, errors.New(err.Error()) + } + + return true, nil +} diff --git a/go.mod b/go.mod index 258b629..6f4fc31 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect diff --git a/go.sum b/go.sum index 63e72d2..4955b4a 100644 --- a/go.sum +++ b/go.sum @@ -256,6 +256,8 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible h1:q+D/Y9jla3afgsIihtyhwyl0c2W+eRWNM9ohVwPiiPw= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=