更新
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (a *App) verificationDocumentHash(value string) []byte {
|
||||
key := a.config.ConfigEncryptionKey
|
||||
if key == "" {
|
||||
key = a.config.JWTSecret + ":development-verification"
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(key+":verification-document"))
|
||||
_, _ = mac.Write([]byte(strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(value), " ", ""))))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
func maskDocumentNumber(value string) string {
|
||||
runes := []rune(strings.TrimSpace(value))
|
||||
if len(runes) <= 4 {
|
||||
return strings.Repeat("*", len(runes))
|
||||
}
|
||||
if len(runes) <= 8 {
|
||||
return string(runes[:1]) + strings.Repeat("*", len(runes)-2) + string(runes[len(runes)-1:])
|
||||
}
|
||||
return string(runes[:3]) + strings.Repeat("*", len(runes)-7) + string(runes[len(runes)-4:])
|
||||
}
|
||||
|
||||
func (a *App) myVerification(w http.ResponseWriter, r *http.Request) {
|
||||
requestedType := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("type")))
|
||||
if requestedType == "" {
|
||||
requestedType = "real_name"
|
||||
}
|
||||
if requestedType != "real_name" && requestedType != "occupation" {
|
||||
fail(w, 400, 20001, "认证类型无效")
|
||||
return
|
||||
}
|
||||
var verificationType, status, realName, documentMask, remark, evidenceJSON string
|
||||
var submittedAt, reviewedAt sql.NullTime
|
||||
err := a.db.QueryRowContext(r.Context(), `SELECT verification_type,status,real_name,document_mask,remark,evidence_json,submitted_at,reviewed_at FROM user_verifications WHERE user_id=? AND verification_type=?`, current(r).ID, requestedType).Scan(&verificationType, &status, &realName, &documentMask, &remark, &evidenceJSON, &submittedAt, &reviewedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
reply(w, map[string]any{"status": "UNVERIFIED", "type": "real_name", "evidence": []string{}})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, 50001, "查询认证状态失败")
|
||||
return
|
||||
}
|
||||
evidence := []string{}
|
||||
_ = json.Unmarshal([]byte(evidenceJSON), &evidence)
|
||||
reply(w, map[string]any{
|
||||
"type": verificationType, "status": status, "realName": realName,
|
||||
"documentMask": documentMask, "remark": remark, "evidence": evidence,
|
||||
"submittedAt": nullableTime(submittedAt), "reviewedAt": nullableTime(reviewedAt),
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) submitVerification(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
RealName string `json:"realName"`
|
||||
DocumentNumber string `json:"documentNumber"`
|
||||
Evidence []string `json:"evidence"`
|
||||
}
|
||||
if decode(r, &req) != nil {
|
||||
fail(w, http.StatusBadRequest, 20001, "认证资料格式错误")
|
||||
return
|
||||
}
|
||||
req.Type = strings.ToLower(strings.TrimSpace(req.Type))
|
||||
if req.Type != "real_name" && req.Type != "occupation" {
|
||||
fail(w, http.StatusBadRequest, 20001, "认证类型无效")
|
||||
return
|
||||
}
|
||||
req.RealName = strings.TrimSpace(req.RealName)
|
||||
documentNumber := strings.ReplaceAll(strings.TrimSpace(req.DocumentNumber), " ", "")
|
||||
if len([]rune(req.RealName)) < 2 || len([]rune(req.RealName)) > 50 || len([]rune(documentNumber)) < 6 || len([]rune(documentNumber)) > 80 {
|
||||
fail(w, http.StatusBadRequest, 20001, "姓名或证件号码格式无效")
|
||||
return
|
||||
}
|
||||
if len(req.Evidence) == 0 || len(req.Evidence) > 3 {
|
||||
fail(w, http.StatusBadRequest, 20001, "请上传 1 至 3 张认证材料")
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := a.db.BeginTx(r.Context(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, 50001, "提交认证失败")
|
||||
return
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
var existingStatus string
|
||||
err = tx.QueryRowContext(r.Context(), `SELECT status FROM user_verifications WHERE user_id=? AND verification_type=? FOR UPDATE`, current(r).ID, req.Type).Scan(&existingStatus)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
fail(w, http.StatusInternalServerError, 50001, "读取认证状态失败")
|
||||
return
|
||||
}
|
||||
if existingStatus == "PENDING" {
|
||||
fail(w, http.StatusConflict, 20001, "认证资料正在审核,请勿重复提交")
|
||||
return
|
||||
}
|
||||
if existingStatus == "VERIFIED" {
|
||||
fail(w, http.StatusConflict, 20001, "账号已完成认证")
|
||||
return
|
||||
}
|
||||
|
||||
evidence := make([]string, 0, len(req.Evidence))
|
||||
seen := map[string]bool{}
|
||||
for _, rawURL := range req.Evidence {
|
||||
mediaURL := strings.TrimSpace(rawURL)
|
||||
if mediaURL == "" || seen[mediaURL] {
|
||||
fail(w, http.StatusBadRequest, 20001, "认证材料地址无效或重复")
|
||||
return
|
||||
}
|
||||
seen[mediaURL] = true
|
||||
var exists int
|
||||
if err = tx.QueryRowContext(r.Context(), `SELECT EXISTS(SELECT 1 FROM media_assets WHERE owner_user_id=? AND public_url=? AND media_type='image' AND status=1)`, current(r).ID, mediaURL).Scan(&exists); err != nil || exists != 1 {
|
||||
fail(w, http.StatusBadRequest, 20001, "认证材料必须由当前账号上传")
|
||||
return
|
||||
}
|
||||
evidence = append(evidence, mediaURL)
|
||||
}
|
||||
evidenceJSON, _ := json.Marshal(evidence)
|
||||
_, err = tx.ExecContext(r.Context(), `INSERT INTO user_verifications(user_id,verification_type,status,real_name,document_mask,document_hash,evidence_json,remark,reviewer_admin_id,submitted_at,reviewed_at)
|
||||
VALUES(?,?,'PENDING',?,?,?,?, '',NULL,NOW(3),NULL)
|
||||
ON DUPLICATE KEY UPDATE verification_type=VALUES(verification_type),status='PENDING',real_name=VALUES(real_name),document_mask=VALUES(document_mask),document_hash=VALUES(document_hash),evidence_json=VALUES(evidence_json),remark='',reviewer_admin_id=NULL,submitted_at=NOW(3),reviewed_at=NULL`, current(r).ID, req.Type, req.RealName, maskDocumentNumber(documentNumber), a.verificationDocumentHash(documentNumber), string(evidenceJSON))
|
||||
if err != nil || tx.Commit() != nil {
|
||||
fail(w, http.StatusInternalServerError, 50001, "提交认证失败")
|
||||
return
|
||||
}
|
||||
reply(w, map[string]any{"status": "PENDING", "submitted": true})
|
||||
}
|
||||
Reference in New Issue
Block a user