user.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. package qiyuesuosdk
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/url"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/common"
  10. user_request "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/user/request"
  11. v2auth_request "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/v2auth/request"
  12. v2auth_response "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/v2auth/response"
  13. )
  14. type userDetailResult struct {
  15. ID flexStringID `json:"id"`
  16. BizID flexStringID `json:"bizId"`
  17. Name string `json:"name"`
  18. Mobile string `json:"mobile"`
  19. Status string `json:"status"`
  20. }
  21. func (r userDetailResult) toUserInfo() *UserInfo {
  22. info := &UserInfo{
  23. ID: r.ID.String(),
  24. BizID: r.BizID.String(),
  25. Name: r.Name,
  26. Mobile: r.Mobile,
  27. Status: r.Status,
  28. }
  29. if info.ID == "" && info.BizID == "" && info.Name == "" {
  30. return nil
  31. }
  32. return info
  33. }
  34. func (p UserLocateParams) toUserQuery() (url.Values, error) {
  35. q := url.Values{}
  36. switch {
  37. case p.UserID != "":
  38. q.Set("id", p.UserID)
  39. case p.Mobile != "":
  40. q.Set("mobile", p.Mobile)
  41. case p.CardNo != "":
  42. q.Set("cardNo", p.CardNo)
  43. case p.OpenUserID != "":
  44. q.Set("bizId", p.OpenUserID)
  45. case p.AccountNo != "":
  46. q.Set("accountNo", p.AccountNo)
  47. default:
  48. return nil, ErrInvalidParams
  49. }
  50. return q, nil
  51. }
  52. func (p UserLocateParams) toUserInfoRequest() (*common.UserInfoRequest, error) {
  53. req := &common.UserInfoRequest{}
  54. switch {
  55. case p.UserID != "":
  56. id, err := strconv.ParseInt(p.UserID, 10, 64)
  57. if err != nil {
  58. return nil, ErrInvalidParams
  59. }
  60. req.UserId = &id
  61. case p.Mobile != "":
  62. req.Mobile = p.Mobile
  63. case p.CardNo != "":
  64. req.CardNo = p.CardNo
  65. case p.OpenUserID != "":
  66. req.OpenUserId = p.OpenUserID
  67. case p.AccountNo != "":
  68. req.AccountNo = p.AccountNo
  69. default:
  70. return nil, ErrInvalidParams
  71. }
  72. return req, nil
  73. }
  74. func isUserNotFound(err error) bool {
  75. if err == nil {
  76. return false
  77. }
  78. msg := err.Error()
  79. return strings.Contains(msg, "用户不存在") || strings.Contains(msg, "未查询到用户") ||
  80. (strings.Contains(msg, "找不到") && strings.Contains(msg, "的用户"))
  81. }
  82. // FindUser 按手机号、证件号、三方用户 id 或登录账号查询契约锁用户;不存在时返回 nil。
  83. func (c *Client) FindUser(p UserLocateParams) (*UserInfo, error) {
  84. q, err := p.toUserQuery()
  85. if err != nil {
  86. return nil, err
  87. }
  88. var resp struct {
  89. apiResponse
  90. Result userDetailResult `json:"result"`
  91. }
  92. if err = c.getQuery("/user", q, &resp); err != nil {
  93. return nil, err
  94. }
  95. if err = resp.err(); err != nil {
  96. if isUserNotFound(err) {
  97. return nil, nil
  98. }
  99. return nil, err
  100. }
  101. return resp.Result.toUserInfo(), nil
  102. }
  103. // FindUserByAccountNo 按 accountNo(通常等于 openUserId)查询用户。
  104. func (c *Client) FindUserByAccountNo(accountNo string) (*UserInfo, error) {
  105. return c.FindUser(UserLocateParams{AccountNo: accountNo})
  106. }
  107. // CreateInternalUser 在契约锁创建内部用户。
  108. func (c *Client) CreateInternalUser(p CreateUserParams) error {
  109. req := user_request.UserV2CreateRequest{
  110. Name: p.Name,
  111. OpenUserId: p.OpenUserID,
  112. AccountNo: p.AccountNo,
  113. Password: p.Password,
  114. Mobile: p.Mobile,
  115. }
  116. if req.AccountNo == "" {
  117. req.AccountNo = p.OpenUserID
  118. }
  119. var resp apiResponse
  120. if err := c.postJSON("/user/v2/create", req, &resp); err != nil {
  121. return err
  122. }
  123. return resp.err()
  124. }
  125. // CreateInternalUserSkipMobileConflict 创建用户;手机号冲突时去掉手机号重试(旧系统行为)。
  126. func (c *Client) CreateInternalUserSkipMobileConflict(p CreateUserParams) error {
  127. err := c.CreateInternalUser(p)
  128. if err == nil {
  129. return nil
  130. }
  131. if p.Mobile != "" && strings.Contains(err.Error(), "手机号已被其他用户绑定") {
  132. p.Mobile = ""
  133. return c.CreateInternalUser(p)
  134. }
  135. return err
  136. }
  137. // UserSignSilentURL 获取个人签名静默授权页;authEnd 为空则默认 10 个月。
  138. // applyCompany 传入当前体检机构(openCompanyId 通常等于 tenant_id),授权记录按机构维度拆分。
  139. func (c *Client) UserSignSilentURL(openUserID string, applyCompany ApplyCompanyParams, authEnd time.Time, completeToPage string) (string, error) {
  140. if authEnd.IsZero() {
  141. authEnd = time.Now().Add(DefaultPersonalAuthDuration)
  142. }
  143. timeEditable := false
  144. req := v2auth_request.V2AuthPersonalsignsilentUrlRequest{
  145. AuthUser: &common.SilentUserRequest{OpenUserId: openUserID},
  146. AuthEndDate: authEnd.Format("2006-01-02"),
  147. AuthTimeModifiable: &timeEditable,
  148. AuthorizedMode: []string{"FACEAUTH", "PINAUTH"},
  149. CompleteToPage: completeToPage,
  150. }
  151. if applyCompany.CompanyID != "" || applyCompany.OpenCompanyID != "" || applyCompany.Name != "" || applyCompany.RegisterNo != "" {
  152. pac := &common.PersonalApplyCompany{
  153. Name: applyCompany.Name,
  154. RegisterNo: applyCompany.RegisterNo,
  155. OpenCompanyId: applyCompany.OpenCompanyID,
  156. }
  157. if applyCompany.CompanyID != "" {
  158. if id, err := strconv.ParseInt(applyCompany.CompanyID, 10, 64); err == nil {
  159. pac.Id = &id
  160. }
  161. }
  162. req.ApplyCompany = pac
  163. }
  164. var resp struct {
  165. apiResponse
  166. Result v2auth_response.V2AuthPersonalsignsilentUrlResponse `json:"result"`
  167. }
  168. if err := c.postJSON("/v2/auth/personalsignsilent/url", req, &resp); err != nil {
  169. return "", err
  170. }
  171. if err := resp.err(); err != nil {
  172. return "", err
  173. }
  174. return resp.Result.Url, nil
  175. }
  176. // QueryPersonalSignAuthRecords 查询个人静默授权记录。
  177. func (c *Client) QueryPersonalSignAuthRecords(openUserID, status string) ([]AuthRecord, error) {
  178. return c.QueryPersonalSignAuthRecordsFor(UserLocateParams{OpenUserID: openUserID}, status)
  179. }
  180. func (c *Client) QueryPersonalSignAuthRecordsFor(user UserLocateParams, status string) ([]AuthRecord, error) {
  181. authUser, err := user.toUserInfoRequest()
  182. if err != nil {
  183. return nil, err
  184. }
  185. req := v2auth_request.V2AuthSignsilentRecordRequest{
  186. AuthUser: authUser,
  187. Status: status,
  188. }
  189. var resp struct {
  190. apiResponse
  191. Result []v2auth_response.V2AuthSignsilentRecordResponse `json:"result"`
  192. }
  193. if err := c.postJSON("/v2/auth/signSilent/record", req, &resp); err != nil {
  194. return nil, err
  195. }
  196. if err := resp.err(); err != nil {
  197. if IsPersonalSignAuthRecordNotFound(err) || isUserNotFound(err) {
  198. return []AuthRecord{}, nil
  199. }
  200. return nil, err
  201. }
  202. out := make([]AuthRecord, 0, len(resp.Result))
  203. for _, item := range resp.Result {
  204. out = append(out, authRecordFromResponse(item))
  205. }
  206. return out, nil
  207. }
  208. func authRecordFromResponse(item v2auth_response.V2AuthSignsilentRecordResponse) AuthRecord {
  209. rec := AuthRecord{
  210. Status: item.Status,
  211. EndTime: item.AuthEndDate,
  212. AuthScope: item.AuthScope,
  213. }
  214. if item.ApplyCompany != nil {
  215. rec.ApplyCompanyID = item.ApplyCompany.Id
  216. rec.ApplyCompanyOpenCompanyID = item.ApplyCompany.OpenCompanyId
  217. rec.ApplyCompanyName = item.ApplyCompany.Name
  218. rec.ApplyCompanyRegisterNo = item.ApplyCompany.RegisterNo
  219. }
  220. return rec
  221. }
  222. // PersonalSignAuthRecordForCompany 查询 openUserId 对指定法人单位的静默授权记录。
  223. func (c *Client) PersonalSignAuthRecordForCompany(openUserID string, company ApplyCompanyParams, status string) (*AuthRecord, bool, error) {
  224. records, err := c.QueryPersonalSignAuthRecords(openUserID, status)
  225. if err != nil {
  226. return nil, false, err
  227. }
  228. if rec, ok := findAuthRecordForCompany(records, company, status); ok {
  229. return rec, true, nil
  230. }
  231. if status != "" {
  232. all, err := c.QueryPersonalSignAuthRecords(openUserID, "")
  233. if err != nil {
  234. return nil, false, err
  235. }
  236. if rec, ok := findAuthRecordForCompany(all, company, status); ok {
  237. return rec, true, nil
  238. }
  239. }
  240. return nil, false, nil
  241. }
  242. func findAuthRecordForCompany(records []AuthRecord, company ApplyCompanyParams, wantStatus string) (*AuthRecord, bool) {
  243. for i := range records {
  244. if wantStatus != "" && records[i].Status != wantStatus {
  245. continue
  246. }
  247. if authRecordMatchesCompany(&records[i], company) {
  248. return &records[i], true
  249. }
  250. }
  251. return nil, false
  252. }
  253. func authRecordMatchesCompany(rec *AuthRecord, company ApplyCompanyParams) bool {
  254. if company.CompanyID != "" && rec.ApplyCompanyID != "" && company.CompanyID == rec.ApplyCompanyID {
  255. return true
  256. }
  257. if company.OpenCompanyID != "" && rec.ApplyCompanyOpenCompanyID != "" && company.OpenCompanyID == rec.ApplyCompanyOpenCompanyID {
  258. return true
  259. }
  260. if company.RegisterNo != "" && rec.ApplyCompanyRegisterNo != "" && company.RegisterNo == rec.ApplyCompanyRegisterNo {
  261. return true
  262. }
  263. return false
  264. }
  265. // IsUserSignAuthorized 是否存在任意有效个人静默授权。
  266. func (c *Client) IsUserSignAuthorized(openUserID string) (bool, error) {
  267. records, err := c.QueryPersonalSignAuthRecords(openUserID, "EFFECT")
  268. if err != nil {
  269. return false, err
  270. }
  271. return len(records) > 0, nil
  272. }
  273. // IsUserSignAuthorizedForCompany 是否已对指定法人单位完成有效个人静默授权。
  274. func (c *Client) IsUserSignAuthorizedForCompany(openUserID string, company ApplyCompanyParams) (bool, error) {
  275. _, found, err := c.PersonalSignAuthRecordForCompany(openUserID, company, "EFFECT")
  276. return found, err
  277. }
  278. // structFormFields 将 struct 转为 multipart 表单字段(与旧系统 HttpMultipart 行为一致)。
  279. func structFormFields(v any, arrayConnector string) (map[string]string, error) {
  280. b, err := json.Marshal(v)
  281. if err != nil {
  282. return nil, err
  283. }
  284. var raw map[string]any
  285. if err = json.Unmarshal(b, &raw); err != nil {
  286. return nil, err
  287. }
  288. out := make(map[string]string, len(raw))
  289. for k, val := range raw {
  290. switch t := val.(type) {
  291. case nil:
  292. continue
  293. case string:
  294. out[k] = t
  295. case bool:
  296. out[k] = strconv.FormatBool(t)
  297. case float64:
  298. out[k] = strconv.FormatInt(int64(t), 10)
  299. case []any:
  300. parts := make([]string, 0, len(t))
  301. for _, item := range t {
  302. parts = append(parts, fmt.Sprint(item))
  303. }
  304. out[k] = strings.Join(parts, arrayConnector)
  305. default:
  306. out[k] = fmt.Sprint(val)
  307. }
  308. }
  309. return out, nil
  310. }