diff --git a/cmd/api/main.go b/cmd/api/main.go index 16ce700..895b525 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -15,6 +15,8 @@ import ( oahandler "wx_service/internal/common/wechat_official/handler" oaservice "wx_service/internal/common/wechat_official/service" "wx_service/internal/database" + lawyerhandler "wx_service/internal/lawyer/handler" + lawyerservice "wx_service/internal/lawyer/service" membershiphandler "wx_service/internal/membership/handler" membershipmodel "wx_service/internal/membership/model" membershipservice "wx_service/internal/membership/service" @@ -88,8 +90,16 @@ func main() { sessionCache = rediscache.NewSessionUserCache(redisClient.Redis(), redisClient.KeyPrefix(), redisClient.SessionTTL()) } + var lawyerHandler *lawyerhandler.LawyerHandler + if lawyerDB, ok := database.GetAdditionalDB("lawyer"); ok { + lawyerService := lawyerservice.NewService(lawyerDB) + lawyerHandler = lawyerhandler.NewLawyerHandler(lawyerService) + } else { + log.Println("lawyer 数据库未配置,/lawyers 接口已禁用") + } + // 6) 注册路由:把 URL 映射到 handler - routes.Register(router, database.DB, authHandler, videoHandler, smokeHandler, redeemCodeHandler, uploadHandler, oaOAuthHandler, sessionCache) + routes.Register(router, database.DB, authHandler, videoHandler, smokeHandler, redeemCodeHandler, uploadHandler, oaOAuthHandler, sessionCache, lawyerHandler) // 7) 启动监听端口 addr := ":" + config.AppConfig.Server.Port diff --git a/docs/README.md b/docs/README.md index acea473..ce47819 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,6 +21,10 @@ - `docs/smoke/README.md` - `docs/smoke/API.md` +## 律师信息上报接口 + +- `docs/lawyer/README.md` + ## 配置 1. 复制 `.env.example` 为 `.env`。 diff --git a/docs/lawyer/README.md b/docs/lawyer/README.md new file mode 100644 index 0000000..d3f8ed1 --- /dev/null +++ b/docs/lawyer/README.md @@ -0,0 +1,35 @@ +# 律师信息上报接口 + +该接口将律师线索数据写入 `lawyer` 库中的 `lawyer` 表(复用 `.env` 中的 `DB_INSTANCES=lawyer` 配置)。接口无需登录即可调用,调用方需自行做风控。 + +## POST `/api/v1/lawyers` + +### 请求参数 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `phone` | string, required | 手机号(会被去除首尾空格) | +| `province` | string | 省份 | +| `city` | string | 城市 | +| `law_firm` | string | 职业律所 | +| `url` | string | 律师详情页链接 | +| `create_time` | int | 创建时间(Unix 时间戳,单位秒,不传则后端使用当前时间) | +| `domain` | string | 来源站点标识 | +| `name` | string | 律师姓名 | +| `params` | string | 附加信息(例如 JSON 字符串) | + +### 响应 + +成功: + +```json +{ + "code": 200, + "message": "success", + "data": { + "id": 146500 + } +} +``` + +失败会返回对应的 HTTP 状态码以及 `code/message`。 diff --git a/internal/lawyer/handler/handler.go b/internal/lawyer/handler/handler.go new file mode 100644 index 0000000..7c6a6c5 --- /dev/null +++ b/internal/lawyer/handler/handler.go @@ -0,0 +1,76 @@ +package handler + +import ( + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + + lawyermodel "wx_service/internal/lawyer/model" + lawyerservice "wx_service/internal/lawyer/service" + "wx_service/internal/model" +) + +type LawyerHandler struct { + service *lawyerservice.Service +} + +func NewLawyerHandler(service *lawyerservice.Service) *LawyerHandler { + return &LawyerHandler{service: service} +} + +type createLawyerRequest struct { + Phone string `json:"phone" binding:"required"` + Province string `json:"province"` + City string `json:"city"` + LawFirm string `json:"law_firm"` + URL string `json:"url"` + CreateTime *int `json:"create_time"` + Domain string `json:"domain"` + Name string `json:"name"` + Params *string `json:"params"` +} + +func (h *LawyerHandler) Create(c *gin.Context) { + var req createLawyerRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误")) + return + } + + phone := strings.TrimSpace(req.Phone) + if phone == "" { + c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "手机号不能为空")) + return + } + + createTime := time.Now().Unix() + if req.CreateTime != nil && *req.CreateTime > 0 { + createTime = int64(*req.CreateTime) + } + + record := &lawyermodel.Lawyer{ + Phone: phone, + Province: strings.TrimSpace(req.Province), + City: strings.TrimSpace(req.City), + LawFirm: strings.TrimSpace(req.LawFirm), + URL: strings.TrimSpace(req.URL), + CreateTime: int(createTime), + Domain: strings.TrimSpace(req.Domain), + Name: strings.TrimSpace(req.Name), + } + + if req.Params != nil { + record.Params = strings.TrimSpace(*req.Params) + } + + if err := h.service.CreateLawyer(c.Request.Context(), record); err != nil { + c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "保存失败,请稍后重试")) + return + } + + c.JSON(http.StatusCreated, model.Success(gin.H{ + "id": record.ID, + })) +} diff --git a/internal/lawyer/model/lawyer.go b/internal/lawyer/model/lawyer.go new file mode 100644 index 0000000..1ffd369 --- /dev/null +++ b/internal/lawyer/model/lawyer.go @@ -0,0 +1,19 @@ +package model + +// Lawyer 对应 lawyer 库中的 lawyer 表(无需自动迁移)。 +type Lawyer struct { + ID uint `gorm:"column:id;primaryKey;autoIncrement"` + Phone string `gorm:"column:phone"` + Province string `gorm:"column:province"` + City string `gorm:"column:city"` + LawFirm string `gorm:"column:law_firm"` + URL string `gorm:"column:url"` + CreateTime int `gorm:"column:create_time"` + Domain string `gorm:"column:domain"` + Name string `gorm:"column:name"` + Params string `gorm:"column:params"` +} + +func (Lawyer) TableName() string { + return "lawyer" +} diff --git a/internal/lawyer/service/service.go b/internal/lawyer/service/service.go new file mode 100644 index 0000000..9348e75 --- /dev/null +++ b/internal/lawyer/service/service.go @@ -0,0 +1,21 @@ +package service + +import ( + "context" + + "gorm.io/gorm" + + lawyermodel "wx_service/internal/lawyer/model" +) + +type Service struct { + db *gorm.DB +} + +func NewService(db *gorm.DB) *Service { + return &Service{db: db} +} + +func (s *Service) CreateLawyer(ctx context.Context, lawyer *lawyermodel.Lawyer) error { + return s.db.WithContext(ctx).Create(lawyer).Error +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index c4e1c71..1b39d06 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -10,6 +10,7 @@ import ( qiniuhandler "wx_service/internal/common/qiniu/handler" rediscache "wx_service/internal/common/redis/cache" oahandler "wx_service/internal/common/wechat_official/handler" + lawyerhandler "wx_service/internal/lawyer/handler" membershiphandler "wx_service/internal/membership/handler" "wx_service/internal/middleware" rmhandler "wx_service/internal/remove_watermark/handler" @@ -26,6 +27,7 @@ func Register( uploadHandler *qiniuhandler.UploadHandler, oaOAuthHandler *oahandler.OAuthHandler, sessionCache *rediscache.SessionUserCache, + lawyerHandler *lawyerhandler.LawyerHandler, ) { // Register 用来集中注册所有 HTTP 路由,便于工程结构更清晰: // - main 只负责初始化(配置/DB/依赖注入) @@ -38,6 +40,10 @@ func Register( // 公众号网页授权:不需要登录(code 本身来自微信授权回调) registerWeChatOfficialRoutes(api, oaOAuthHandler) + if lawyerHandler != nil { + api.POST("/lawyers", lawyerHandler.Create) + } + // 需要登录的接口组:统一挂载鉴权中间件 protected := api.Group("") protected.Use(middleware.AuthMiddleware(db, sessionCache))