seal.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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. func pickAuthorizedCompanySeal(seals []AuthorizedCompanySeal, preferredName string) (*AuthorizedCompanySeal, error) {
  138. if len(seals) == 0 {
  139. return nil, ErrSealNotFound
  140. }
  141. preferredName = strings.TrimSpace(preferredName)
  142. if preferredName != "" {
  143. for i := range seals {
  144. if seals[i].Name == preferredName {
  145. return &seals[i], nil
  146. }
  147. }
  148. }
  149. for i := range seals {
  150. if seals[i].Type == "COMPANY" {
  151. return &seals[i], nil
  152. }
  153. }
  154. return &seals[0], nil
  155. }
  156. // SealImageByID 按印章 id 下载电子章图片。
  157. func (c *Client) SealImageByID(company CompanyLocateParams, sealID, sealName string) ([]byte, string, error) {
  158. sealID = strings.TrimSpace(sealID)
  159. if sealID == "" {
  160. return nil, "", ErrInvalidParams
  161. }
  162. body := map[string]any{
  163. "sealId": sealID,
  164. "sealAttribute": "ELECTRONIC",
  165. "sealImageFormatRequest": map[string]any{
  166. "imageFormat": "png",
  167. },
  168. }
  169. if sealName != "" {
  170. body["sealName"] = sealName
  171. }
  172. if company.ID != "" {
  173. body["companyId"] = company.ID
  174. }
  175. if company.Name != "" {
  176. body["companyName"] = company.Name
  177. }
  178. if company.RegisterNo != "" {
  179. body["registerNo"] = company.RegisterNo
  180. }
  181. return c.postForImageBytes("/seal/customparam/image", body)
  182. }
  183. // ResolveCompanySealImageBase64 从静默授权记录取已授权印章,再按 sealId 拉图。
  184. func (c *Client) ResolveCompanySealImageBase64(company CompanyLocateParams, preferredSealName string) (string, string, string, error) {
  185. seals, err := c.ListAuthorizedCompanySeals(company, "EFFECT")
  186. if err != nil {
  187. return "", "", "", err
  188. }
  189. seal, err := pickAuthorizedCompanySeal(seals, preferredSealName)
  190. if err != nil {
  191. // 授权记录未返回印章详情时,再尝试印章列表(部分环境已分配查看权限)。
  192. certSeals, listErr := c.ListCompanyElectronicSeals(company)
  193. if listErr != nil {
  194. return "", "", "", err
  195. }
  196. certSeal, pickErr := pickCompanySeal(certSeals, preferredSealName)
  197. if pickErr != nil {
  198. return "", "", "", err
  199. }
  200. seal = &AuthorizedCompanySeal{
  201. ID: certSeal.ID,
  202. Name: certSeal.Name,
  203. Type: certSeal.Type,
  204. }
  205. }
  206. data, contentType, err := c.SealImageByID(company, seal.ID, seal.Name)
  207. if err != nil && seal.Name != "" {
  208. data, contentType, err = c.CompanySealImage(company, seal.Name)
  209. }
  210. if err != nil {
  211. return "", "", "", err
  212. }
  213. if len(data) == 0 {
  214. return "", "", "", ErrSealImageUnavailable
  215. }
  216. return seal.Name, base64.StdEncoding.EncodeToString(data), contentType, nil
  217. }
  218. // CompanySignQualifications 法人单位签章资质(是否已有可用电子章等)。
  219. type CompanySignQualifications struct {
  220. ElectronicSeal bool `json:"electronicSeal"`
  221. SM2Cert bool `json:"sm2Cert"`
  222. RSACert bool `json:"rsaCert"`
  223. }
  224. type companySignQualificationsResp struct {
  225. apiResponse
  226. Result CompanySignQualifications `json:"result"`
  227. }
  228. type sealCreateResp struct {
  229. apiResponse
  230. Result v2seal_response.V2SealCreateResponse `json:"result"`
  231. }
  232. // UserPortalURL 由开放平台 API 地址推导用户前台地址(制章/登录)。
  233. func UserPortalURL(apiAddress string) string {
  234. addr := strings.TrimRight(apiAddress, "/")
  235. if strings.Contains(addr, ":9182") {
  236. return strings.Replace(addr, ":9182", ":9180", 1)
  237. }
  238. return addr
  239. }
  240. // CompanySignQualificationsFor 查询法人单位是否具备电子印章等签章资质。
  241. func (c *Client) CompanySignQualificationsFor(company CompanyLocateParams) (*CompanySignQualifications, error) {
  242. companyReq, err := company.toCompanyRequest()
  243. if err != nil {
  244. return nil, err
  245. }
  246. var resp companySignQualificationsResp
  247. if err := c.postJSON("/company/sign/qualifications", map[string]any{
  248. "companyRequest": companyReq,
  249. }, &resp); err != nil {
  250. return nil, err
  251. }
  252. if err := resp.err(); err != nil {
  253. return nil, err
  254. }
  255. return &resp.Result, nil
  256. }
  257. // CompanySealCreatePageURL 获取制作电子公章的页面链接(method=page)。
  258. func (c *Client) CompanySealCreatePageURL(company CompanyLocateParams, companyDisplayName, sealName, operatorMobile string) (string, error) {
  259. if sealName == "" || operatorMobile == "" {
  260. return "", ErrInvalidParams
  261. }
  262. displayName := companyDisplayName
  263. if displayName == "" {
  264. displayName = company.Name
  265. }
  266. comp := &common.Company{Name: displayName}
  267. if company.RegisterNo != "" {
  268. comp.RegisterNo = company.RegisterNo
  269. }
  270. if company.ID != "" {
  271. if id, err := strconv.ParseInt(company.ID, 10, 64); err == nil {
  272. comp.Id = &id
  273. }
  274. }
  275. modify := true
  276. req := v2seal_request.V2SealCreateRequest{
  277. BusinessType: "ELECTRONIC",
  278. SealType: "COMPANY",
  279. Method: "page",
  280. Name: sealName,
  281. SealCategoryName: "公章",
  282. Company: comp,
  283. OperatorInfo: &common.UserInfoRequest{
  284. Mobile: operatorMobile,
  285. },
  286. Modify: &modify,
  287. }
  288. var resp sealCreateResp
  289. if err := c.postJSON("/v2/seal/create", req, &resp); err != nil {
  290. return "", err
  291. }
  292. if err := resp.err(); err != nil {
  293. return "", err
  294. }
  295. url := resp.Result.PageUrl
  296. if url == "" {
  297. url = resp.Result.LocalPageUrl
  298. }
  299. if url == "" {
  300. return "", ErrSealCreatePageUnavailable
  301. }
  302. return url, nil
  303. }
  304. // IsSealNotFoundError 静默授权或查章时「查询的印章不存在」。
  305. func IsSealNotFoundError(err error) bool {
  306. if err == nil {
  307. return false
  308. }
  309. if errors.Is(err, ErrSealNotFound) {
  310. return true
  311. }
  312. msg := err.Error()
  313. return strings.Contains(msg, "查询的印章不存在") || strings.Contains(msg, "无可授权的印章")
  314. }