seal.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. package qiyuesuosdk
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "strconv"
  6. "strings"
  7. v2seal_request "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/v2seal/request"
  8. v2seal_response "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/v2seal/response"
  9. "git.sxidc.com/student-physical-examination/contract_lock_sdk/model/common"
  10. )
  11. // DeriveCompanySealName 根据机构名称推导契约锁公章名称(与制章/授权逻辑一致)。
  12. func DeriveCompanySealName(companyDisplayName string) string {
  13. sealName := strings.TrimSpace(companyDisplayName)
  14. if sealName == "" {
  15. return "机构公章"
  16. }
  17. if !strings.Contains(sealName, "章") {
  18. sealName += "公章"
  19. }
  20. return sealName
  21. }
  22. // CompanySealImage 下载机构电子公章图片(PNG)。
  23. func (c *Client) CompanySealImage(company CompanyLocateParams, sealName string) ([]byte, string, error) {
  24. sealName = strings.TrimSpace(sealName)
  25. if sealName == "" {
  26. return nil, "", ErrInvalidParams
  27. }
  28. body := map[string]any{
  29. "sealName": sealName,
  30. "sealAttribute": "ELECTRONIC",
  31. "sealImageFormatRequest": map[string]any{
  32. "imageFormat": "png",
  33. },
  34. }
  35. if company.ID != "" {
  36. body["companyId"] = company.ID
  37. }
  38. if company.Name != "" {
  39. body["companyName"] = company.Name
  40. }
  41. if company.RegisterNo != "" {
  42. body["registerNo"] = company.RegisterNo
  43. }
  44. data, contentType, err := c.postForImageBytes("/seal/customparam/image", body)
  45. if err != nil {
  46. return nil, "", err
  47. }
  48. if len(data) == 0 {
  49. return nil, "", ErrSealImageUnavailable
  50. }
  51. return data, contentType, nil
  52. }
  53. // CompanySealImageBase64 下载机构公章并返回标准 Base64(不含 data: 前缀)。
  54. func (c *Client) CompanySealImageBase64(company CompanyLocateParams, sealName string) (string, string, error) {
  55. data, contentType, err := c.CompanySealImage(company, sealName)
  56. if err != nil {
  57. return "", "", err
  58. }
  59. return base64.StdEncoding.EncodeToString(data), contentType, nil
  60. }
  61. // CompanySealSummary 法人单位电子印章摘要(来自 /seal/customparam/cert/list)。
  62. type CompanySealSummary struct {
  63. ID string
  64. Name string
  65. Type string
  66. SealAttribute string
  67. StatusKey string
  68. }
  69. type sealCertListItem struct {
  70. ID string `json:"id"`
  71. Name string `json:"name"`
  72. Type string `json:"type"`
  73. SealAttribute string `json:"sealAttribute"`
  74. Status *struct {
  75. Key string `json:"key"`
  76. } `json:"status"`
  77. }
  78. type sealCertListResp struct {
  79. apiResponse
  80. List []sealCertListItem `json:"list"`
  81. }
  82. // ListCompanyElectronicSeals 查询法人单位下状态正常的电子印章列表。
  83. func (c *Client) ListCompanyElectronicSeals(company CompanyLocateParams) ([]CompanySealSummary, error) {
  84. companyReq, err := company.toCompanyRequest()
  85. if err != nil {
  86. return nil, err
  87. }
  88. var resp sealCertListResp
  89. if err := c.postJSON("/seal/customparam/cert/list", map[string]any{
  90. "companyRequest": companyReq,
  91. "sealAttribute": "ELECTRONIC",
  92. "sealQueryStatus": "NORMAL",
  93. }, &resp); err != nil {
  94. return nil, err
  95. }
  96. if err := resp.err(); err != nil {
  97. return nil, err
  98. }
  99. out := make([]CompanySealSummary, 0, len(resp.List))
  100. for _, item := range resp.List {
  101. statusKey := ""
  102. if item.Status != nil {
  103. statusKey = item.Status.Key
  104. }
  105. if statusKey != "" && statusKey != "NORMAL" {
  106. continue
  107. }
  108. out = append(out, CompanySealSummary{
  109. ID: item.ID,
  110. Name: item.Name,
  111. Type: item.Type,
  112. SealAttribute: item.SealAttribute,
  113. StatusKey: statusKey,
  114. })
  115. }
  116. return out, nil
  117. }
  118. func pickCompanySeal(seals []CompanySealSummary, preferredName string) (*CompanySealSummary, error) {
  119. if len(seals) == 0 {
  120. return nil, ErrSealNotFound
  121. }
  122. preferredName = strings.TrimSpace(preferredName)
  123. if preferredName != "" {
  124. for i := range seals {
  125. if seals[i].Name == preferredName {
  126. return &seals[i], nil
  127. }
  128. }
  129. }
  130. for i := range seals {
  131. if seals[i].Type == "COMPANY" {
  132. return &seals[i], nil
  133. }
  134. }
  135. return &seals[0], nil
  136. }
  137. // SealImageByID 按印章 id 下载电子章图片。
  138. func (c *Client) SealImageByID(sealID string) ([]byte, string, error) {
  139. sealID = strings.TrimSpace(sealID)
  140. if sealID == "" {
  141. return nil, "", ErrInvalidParams
  142. }
  143. return c.postForImageBytes("/seal/customparam/image", map[string]any{
  144. "sealId": sealID,
  145. "sealAttribute": "ELECTRONIC",
  146. "sealImageFormatRequest": map[string]any{
  147. "imageFormat": "png",
  148. },
  149. })
  150. }
  151. // ResolveCompanySealImageBase64 先列出法人单位电子章,再按 sealId 拉图(避免印章名不一致)。
  152. func (c *Client) ResolveCompanySealImageBase64(company CompanyLocateParams, preferredSealName string) (string, string, string, error) {
  153. seals, err := c.ListCompanyElectronicSeals(company)
  154. if err != nil {
  155. return "", "", "", err
  156. }
  157. seal, err := pickCompanySeal(seals, preferredSealName)
  158. if err != nil {
  159. return "", "", "", err
  160. }
  161. data, contentType, err := c.SealImageByID(seal.ID)
  162. if err != nil {
  163. return "", "", "", err
  164. }
  165. if len(data) == 0 {
  166. return "", "", "", ErrSealImageUnavailable
  167. }
  168. return seal.Name, base64.StdEncoding.EncodeToString(data), contentType, nil
  169. }
  170. // CompanySignQualifications 法人单位签章资质(是否已有可用电子章等)。
  171. type CompanySignQualifications struct {
  172. ElectronicSeal bool `json:"electronicSeal"`
  173. SM2Cert bool `json:"sm2Cert"`
  174. RSACert bool `json:"rsaCert"`
  175. }
  176. type companySignQualificationsResp struct {
  177. apiResponse
  178. Result CompanySignQualifications `json:"result"`
  179. }
  180. type sealCreateResp struct {
  181. apiResponse
  182. Result v2seal_response.V2SealCreateResponse `json:"result"`
  183. }
  184. // UserPortalURL 由开放平台 API 地址推导用户前台地址(制章/登录)。
  185. func UserPortalURL(apiAddress string) string {
  186. addr := strings.TrimRight(apiAddress, "/")
  187. if strings.Contains(addr, ":9182") {
  188. return strings.Replace(addr, ":9182", ":9180", 1)
  189. }
  190. return addr
  191. }
  192. // CompanySignQualificationsFor 查询法人单位是否具备电子印章等签章资质。
  193. func (c *Client) CompanySignQualificationsFor(company CompanyLocateParams) (*CompanySignQualifications, error) {
  194. companyReq, err := company.toCompanyRequest()
  195. if err != nil {
  196. return nil, err
  197. }
  198. var resp companySignQualificationsResp
  199. if err := c.postJSON("/company/sign/qualifications", map[string]any{
  200. "companyRequest": companyReq,
  201. }, &resp); err != nil {
  202. return nil, err
  203. }
  204. if err := resp.err(); err != nil {
  205. return nil, err
  206. }
  207. return &resp.Result, nil
  208. }
  209. // CompanySealCreatePageURL 获取制作电子公章的页面链接(method=page)。
  210. func (c *Client) CompanySealCreatePageURL(company CompanyLocateParams, companyDisplayName, sealName, operatorMobile string) (string, error) {
  211. if sealName == "" || operatorMobile == "" {
  212. return "", ErrInvalidParams
  213. }
  214. displayName := companyDisplayName
  215. if displayName == "" {
  216. displayName = company.Name
  217. }
  218. comp := &common.Company{Name: displayName}
  219. if company.RegisterNo != "" {
  220. comp.RegisterNo = company.RegisterNo
  221. }
  222. if company.ID != "" {
  223. if id, err := strconv.ParseInt(company.ID, 10, 64); err == nil {
  224. comp.Id = &id
  225. }
  226. }
  227. modify := true
  228. req := v2seal_request.V2SealCreateRequest{
  229. BusinessType: "ELECTRONIC",
  230. SealType: "COMPANY",
  231. Method: "page",
  232. Name: sealName,
  233. SealCategoryName: "公章",
  234. Company: comp,
  235. OperatorInfo: &common.UserInfoRequest{
  236. Mobile: operatorMobile,
  237. },
  238. Modify: &modify,
  239. }
  240. var resp sealCreateResp
  241. if err := c.postJSON("/v2/seal/create", req, &resp); err != nil {
  242. return "", err
  243. }
  244. if err := resp.err(); err != nil {
  245. return "", err
  246. }
  247. url := resp.Result.PageUrl
  248. if url == "" {
  249. url = resp.Result.LocalPageUrl
  250. }
  251. if url == "" {
  252. return "", ErrSealCreatePageUnavailable
  253. }
  254. return url, nil
  255. }
  256. // IsSealNotFoundError 静默授权或查章时「查询的印章不存在」。
  257. func IsSealNotFoundError(err error) bool {
  258. if err == nil {
  259. return false
  260. }
  261. if errors.Is(err, ErrSealNotFound) {
  262. return true
  263. }
  264. msg := err.Error()
  265. return strings.Contains(msg, "查询的印章不存在") || strings.Contains(msg, "无可授权的印章")
  266. }