| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- package centralpayment
- import (
- "net/http"
- "strconv"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- "go.uber.org/zap"
- "wails-app/internal/global"
- "wails-app/internal/modules/central-payment/model/request"
- responseModel "wails-app/internal/modules/central-payment/model/response"
- centralService "wails-app/internal/modules/central-payment/service"
- )
- var svc = centralService.New()
- func requestFields(c *gin.Context, endpoint string, fields ...zap.Field) []zap.Field {
- base := []zap.Field{
- zap.String("endpoint", endpoint),
- zap.String("source_ip", c.ClientIP()),
- zap.String("content_type", c.ContentType()),
- zap.Int64("content_length", c.Request.ContentLength),
- }
- return append(base, fields...)
- }
- func logInfo(c *gin.Context, message, endpoint string, fields ...zap.Field) {
- if global.GVA_LOG != nil {
- global.GVA_LOG.Info(message, requestFields(c, endpoint, fields...)...)
- }
- }
- func logWarn(c *gin.Context, message, endpoint string, fields ...zap.Field) {
- if global.GVA_LOG != nil {
- global.GVA_LOG.Warn(message, requestFields(c, endpoint, fields...)...)
- }
- }
- func passwordFormat(password string) string {
- trimmed := strings.TrimSpace(password)
- if trimmed == "" {
- return "empty"
- }
- if len(trimmed) == 32 {
- isHex := true
- for _, ch := range trimmed {
- if !((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
- isHex = false
- break
- }
- }
- if isHex {
- return "md5-32-hex_unsupported"
- }
- }
- if len(trimmed) == 60 && (strings.HasPrefix(trimmed, "$2a$") || strings.HasPrefix(trimmed, "$2b$") || strings.HasPrefix(trimmed, "$2y$")) {
- return "bcrypt-hash_unsupported"
- }
- return "plain-text_expected"
- }
- func fail(c *gin.Context, endpoint, deviceID, remark string, body interface{}) {
- svc.Log(endpoint, deviceID, c.ClientIP(), body, 0, remark)
- logWarn(c, "中央缴费机请求处理失败", endpoint, zap.String("device_id", deviceID), zap.String("reason", remark))
- c.JSON(http.StatusOK, responseModel.Envelope{Result: 0, Data: struct{}{}, ReMark: remark})
- }
- func Login(c *gin.Context) {
- var req request.LoginRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- logWarn(c, "中央缴费机收到无法解析的登录请求", "/api/login", zap.Error(err))
- fail(c, "/api/login", "", "请求参数错误", struct{}{})
- return
- }
- format := passwordFormat(req.Password)
- logInfo(c, "中央缴费机收到登录请求", "/api/login",
- zap.String("username", strings.TrimSpace(req.UserName)),
- zap.String("password_format", format), zap.Int("password_length", len(req.Password)))
- token, err := svc.Login(req)
- if err != nil {
- fail(c, "/api/login", "", err.Error(), map[string]interface{}{
- "username": strings.TrimSpace(req.UserName), "password_format": format, "password_length": len(req.Password),
- })
- return
- }
- svc.Log("/api/login", "", c.ClientIP(), map[string]interface{}{
- "username": strings.TrimSpace(req.UserName), "password_format": format, "password_length": len(req.Password),
- }, 1, "success")
- logInfo(c, "中央缴费机登录成功", "/api/login", zap.String("username", strings.TrimSpace(req.UserName)))
- c.JSON(http.StatusOK, responseModel.LoginResponse{Result: 1, Token: token, ReMark: "success"})
- }
- func QueryPrice(c *gin.Context) {
- var req request.QueryPriceRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- logWarn(c, "中央缴费机收到无法解析的费用查询请求", "/api/queryprice", zap.Error(err))
- fail(c, "/api/queryprice", "", "请求参数错误", struct{}{})
- return
- }
- logInfo(c, "中央缴费机收到费用查询请求", "/api/queryprice", zap.String("car_no", req.Data.CarNo), zap.Bool("token_present", strings.TrimSpace(req.Token) != ""))
- device, err := svc.Authenticate(req.Token)
- if err != nil {
- fail(c, "/api/queryprice", "", err.Error(), req.Data)
- return
- }
- price, err := svc.QueryPrice(device, req.Data.CarNo)
- if err != nil {
- fail(c, "/api/queryprice", device.DeviceID, err.Error(), req.Data)
- return
- }
- data := responseModel.QueryPriceData{
- OrderID: price.OrderID, CarNo: price.CarNo,
- InTime: price.InTime.Format("2006-01-02 15:04:05"),
- OutTime: price.OutTime.Format("2006-01-02 15:04:05"),
- StopTime: price.StopTime, Price: price.Price,
- CardNo: "", Remark: "系统实时计费", InPic: "",
- }
- svc.Log("/api/queryprice", device.DeviceID, c.ClientIP(), req.Data, 1, "success")
- logInfo(c, "中央缴费机费用查询成功", "/api/queryprice", zap.String("device_id", device.DeviceID), zap.String("car_no", req.Data.CarNo), zap.String("order_id", price.OrderID))
- c.JSON(http.StatusOK, responseModel.Envelope{Result: 1, Data: data, ReMark: "success"})
- }
- func PaySuccess(c *gin.Context) {
- var req request.PaySuccessRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- logWarn(c, "中央缴费机收到无法解析的支付通知", "/api/paysuccess", zap.Error(err))
- fail(c, "/api/paysuccess", "", "请求参数错误", struct{}{})
- return
- }
- logInfo(c, "中央缴费机收到支付通知", "/api/paysuccess",
- zap.String("order_id", strings.TrimSpace(req.Data.OrderID)),
- zap.String("car_no", strings.TrimSpace(req.Data.CarNo)),
- zap.String("device_id", strings.TrimSpace(req.Data.DevID)),
- zap.Int("pay_type", req.Data.PayType),
- zap.Int("pay_dev", req.Data.PayDev),
- zap.String("money_raw", strings.TrimSpace(string(req.Data.Money))),
- zap.String("pay_time", req.Data.PayTime),
- zap.Bool("token_present", strings.TrimSpace(req.Token) != ""))
- device, err := svc.Authenticate(req.Token)
- if err != nil {
- fail(c, "/api/paysuccess", "", err.Error(), req.Data)
- return
- }
- callbackDeviceID := strings.TrimSpace(req.Data.DevID)
- logInfo(c, "中央缴费机支付身份校验", "/api/paysuccess",
- zap.String("token_device_id", device.DeviceID),
- zap.String("callback_device_id", callbackDeviceID),
- zap.Bool("device_id_match", callbackDeviceID == device.DeviceID))
- if err := svc.PaySuccess(device, req); err != nil {
- fail(c, "/api/paysuccess", device.DeviceID, err.Error(), req.Data)
- return
- }
- svc.Log("/api/paysuccess", device.DeviceID, c.ClientIP(), req.Data, 1, "success")
- logInfo(c, "中央缴费机支付通知处理成功", "/api/paysuccess", zap.String("device_id", device.DeviceID), zap.String("order_id", strings.TrimSpace(req.Data.OrderID)), zap.String("money_raw", strings.TrimSpace(string(req.Data.Money))))
- c.JSON(http.StatusOK, responseModel.Envelope{Result: 1, Data: struct{}{}, ReMark: "success"})
- }
- func PayStatus(c *gin.Context) {
- var req request.PayStatusRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- logWarn(c, "中央缴费机收到无法解析的支付状态查询", "/api/paystatus", zap.Error(err))
- fail(c, "/api/paystatus", "", "请求参数错误", struct{}{})
- return
- }
- logInfo(c, "中央缴费机收到支付状态查询", "/api/paystatus",
- zap.String("order_id", strings.TrimSpace(req.Data.OrderID)),
- zap.String("car_no", strings.TrimSpace(req.Data.CarNo)),
- zap.String("device_id", strings.TrimSpace(req.Data.DevID)),
- zap.Bool("token_present", strings.TrimSpace(req.Token) != ""))
- device, err := svc.Authenticate(req.Token)
- if err != nil {
- fail(c, "/api/paystatus", "", err.Error(), req.Data)
- return
- }
- callbackDeviceID := strings.TrimSpace(req.Data.DevID)
- logInfo(c, "中央缴费机支付状态身份校验", "/api/paystatus",
- zap.String("token_device_id", device.DeviceID),
- zap.String("callback_device_id", callbackDeviceID),
- zap.Bool("device_id_match", callbackDeviceID == "" || callbackDeviceID == device.DeviceID))
- ticket, record, err := svc.PayStatus(device, req)
- if err != nil {
- fail(c, "/api/paystatus", device.DeviceID, err.Error(), req.Data)
- return
- }
- payStatus := "unpaid"
- if ticket.State == "paid" || ticket.State == "exited" || record.PaymentStatus == "paid" {
- payStatus = "paid"
- }
- payTime := ""
- if ticket.PaidAt != nil {
- payTime = ticket.PaidAt.Format("2006-01-02 15:04:05")
- }
- data := responseModel.PayStatusData{
- OrderID: ticket.TicketNo, CarNo: ticket.PlateNumber, State: ticket.State,
- PayStatus: payStatus, PaymentStatus: record.PaymentStatus,
- Fee: strconv.FormatFloat(ticket.Fee, 'f', 2, 64), Money: strconv.FormatFloat(ticket.PaidAmount, 'f', 2, 64),
- PaidAmount: strconv.FormatFloat(ticket.PaidAmount, 'f', 2, 64), PayTime: payTime,
- }
- svc.Log("/api/paystatus", device.DeviceID, c.ClientIP(), req.Data, 1, "success")
- logInfo(c, "中央缴费机支付状态查询成功", "/api/paystatus",
- zap.String("device_id", device.DeviceID), zap.String("order_id", ticket.TicketNo),
- zap.String("state", ticket.State), zap.String("record_payment_status", record.PaymentStatus),
- zap.String("pay_status", payStatus), zap.Float64("fee", ticket.Fee),
- zap.Float64("paid_amount", ticket.PaidAmount), zap.String("payment_entry", ticket.PaymentEntry),
- zap.String("payment_method", ticket.PaymentMethod), zap.String("pay_time", payTime))
- c.JSON(http.StatusOK, responseModel.Envelope{Result: 1, Data: data, ReMark: "success"})
- }
- func Heartbeat(c *gin.Context) {
- var req request.HeartbeatRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- logWarn(c, "中央缴费机收到无法解析的心跳请求", "/api/heartbeat", zap.Error(err))
- fail(c, "/api/heartbeat", "", "请求参数错误", struct{}{})
- return
- }
- logInfo(c, "中央缴费机收到心跳请求", "/api/heartbeat", zap.Bool("token_present", strings.TrimSpace(req.Token) != ""))
- device, err := svc.Authenticate(req.Token)
- if err != nil {
- fail(c, "/api/heartbeat", "", err.Error(), struct{}{})
- return
- }
- data := map[string]string{"SystemTime": time.Now().Format("2006-01-02 15:04:05")}
- svc.Log("/api/heartbeat", device.DeviceID, c.ClientIP(), struct{}{}, 1, "success")
- logInfo(c, "中央缴费机心跳处理成功", "/api/heartbeat", zap.String("device_id", device.DeviceID))
- c.JSON(http.StatusOK, responseModel.Envelope{Result: 1, Data: data, ReMark: "success"})
- }
- // SetupPublicRouter is intentionally unauthenticated at the Gin level. Each
- // POF endpoint applies its own isolated device-token authentication.
- func SetupPublicRouter(router *gin.RouterGroup) {
- group := router.Group("/api")
- group.POST("/login", Login)
- group.POST("/queryprice", QueryPrice)
- group.POST("/paysuccess", PaySuccess)
- group.POST("/paystatus", PayStatus)
- group.POST("/heartbeat", Heartbeat)
- }
|