修改二板涨停情况查询,新增按条件筛选
This commit is contained in:
@@ -28,10 +28,10 @@ type secondProfitCSVRow struct {
|
||||
Name string `gorm:"column:name"`
|
||||
Market string `gorm:"column:market"`
|
||||
Industry string `gorm:"column:industry"`
|
||||
Open float64 `gorm:"column:open"`
|
||||
Close float64 `gorm:"column:close_price"`
|
||||
Open sql.NullFloat64 `gorm:"column:open"`
|
||||
Close sql.NullFloat64 `gorm:"column:close_price"`
|
||||
LimitPrice float64 `gorm:"column:limit_price"`
|
||||
PreviousClose float64 `gorm:"column:previous_close"`
|
||||
PreviousClose sql.NullFloat64 `gorm:"column:previous_close"`
|
||||
FirstLimitTime sql.NullString `gorm:"column:tushare_first_time"`
|
||||
PctChg sql.NullFloat64 `gorm:"column:pct_chg"`
|
||||
Amount sql.NullFloat64 `gorm:"column:tushare_amount"`
|
||||
@@ -44,30 +44,128 @@ type secondProfitCSVRow struct {
|
||||
NextLow sql.NullFloat64 `gorm:"column:next_low"`
|
||||
NextClose sql.NullFloat64 `gorm:"column:next_close"`
|
||||
OpenPreviousCloseRatio float64
|
||||
OpenPctChg sql.NullFloat64
|
||||
NextOpenReturnPct sql.NullFloat64
|
||||
NextHighReturnPct sql.NullFloat64
|
||||
NextLowReturnPct sql.NullFloat64
|
||||
NextCloseReturnPct sql.NullFloat64
|
||||
NextOpenProfitable string
|
||||
NextCloseProfitable string
|
||||
NextIntradayProfitable string
|
||||
}
|
||||
|
||||
func (h *LimitBoardHandler) DownloadSecondBoardProfitCSV(c *gin.Context) {
|
||||
startDate := c.DefaultQuery("start_date", defaultSecondProfitStartDate)
|
||||
endDate := c.DefaultQuery("end_date", defaultSecondProfitEndDate)
|
||||
if err := validateSecondProfitDateRange(startDate, endDate); err != nil {
|
||||
type secondProfitQuery struct {
|
||||
TradeStartDate string
|
||||
TradeEndDate string
|
||||
LimitUpTimeFilterType1 string
|
||||
LimitUpTimeFilterValue1 string
|
||||
LimitUpTimeFilterType2 string
|
||||
LimitUpTimeFilterValue2 string
|
||||
OpenPctChgFilterType1 string
|
||||
OpenPctChgFilterValue1 string
|
||||
OpenPctChgFilterType2 string
|
||||
OpenPctChgFilterValue2 string
|
||||
LimitUpTimeRule secondProfitCompareFilter
|
||||
OpenPctChgRule secondProfitCompareFilter
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type secondProfitCompareFilter struct {
|
||||
Conditions []secondProfitCompareCondition
|
||||
}
|
||||
|
||||
type secondProfitCompareCondition struct {
|
||||
Op string
|
||||
Value float64
|
||||
}
|
||||
|
||||
type secondProfitListItem struct {
|
||||
TradeDate string `json:"trade_date"`
|
||||
TsCode string `json:"ts_code"`
|
||||
Name string `json:"name"`
|
||||
Market string `json:"market"`
|
||||
Industry string `json:"industry"`
|
||||
NextOpenProfitable string `json:"next_open_profitable"`
|
||||
NextCloseProfitable string `json:"next_close_profitable"`
|
||||
NextIntradayProfitable string `json:"next_intraday_profitable"`
|
||||
NextHighProfitPct *float64 `json:"next_high_profit_pct"`
|
||||
NextLowProfitPct *float64 `json:"next_low_profit_pct"`
|
||||
BreakLimitCount *int64 `json:"break_limit_count"`
|
||||
TurnoverRatePct *float64 `json:"turnover_rate_pct"`
|
||||
FirstLimitTime string `json:"first_limit_time"`
|
||||
SealAmount *float64 `json:"seal_amount"`
|
||||
Amount *float64 `json:"amount"`
|
||||
}
|
||||
|
||||
func (h *LimitBoardHandler) ListSecondBoardProfit(c *gin.Context) {
|
||||
req, err := parseSecondProfitQuery(c, true)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := h.loadSecondProfitCSVRows(startDate, endDate)
|
||||
rows, err := h.loadSecondProfitRows(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
total := len(rows)
|
||||
start := (req.Page - 1) * req.PageSize
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
end := start + req.PageSize
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
|
||||
items := make([]secondProfitListItem, 0, end-start)
|
||||
for _, row := range rows[start:end] {
|
||||
items = append(items, secondProfitBuildItem(row))
|
||||
}
|
||||
|
||||
totalPages := 0
|
||||
if total > 0 {
|
||||
totalPages = (total + req.PageSize - 1) / req.PageSize
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"trade_start_date": req.TradeStartDate,
|
||||
"trade_end_date": req.TradeEndDate,
|
||||
"limit_up_time_filter_type1": req.LimitUpTimeFilterType1,
|
||||
"limit_up_time_filter_value1": req.LimitUpTimeFilterValue1,
|
||||
"limit_up_time_filter_type2": req.LimitUpTimeFilterType2,
|
||||
"limit_up_time_filter_value2": req.LimitUpTimeFilterValue2,
|
||||
"open_pct_chg_filter_type1": req.OpenPctChgFilterType1,
|
||||
"open_pct_chg_filter_value1": req.OpenPctChgFilterValue1,
|
||||
"open_pct_chg_filter_type2": req.OpenPctChgFilterType2,
|
||||
"open_pct_chg_filter_value2": req.OpenPctChgFilterValue2,
|
||||
"page": req.Page,
|
||||
"page_size": req.PageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
"list": items,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *LimitBoardHandler) DownloadSecondBoardProfitCSV(c *gin.Context) {
|
||||
req, err := parseSecondProfitQuery(c, false)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := h.loadSecondProfitRows(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("limit_second_board_profit_%s_%s_daily_detail.csv",
|
||||
strings.ReplaceAll(startDate, "-", ""),
|
||||
strings.ReplaceAll(endDate, "-", ""),
|
||||
strings.ReplaceAll(req.TradeStartDate, "-", ""),
|
||||
strings.ReplaceAll(req.TradeEndDate, "-", ""),
|
||||
)
|
||||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||||
c.Header("Content-Disposition", `attachment; filename="`+filename+`"`)
|
||||
@@ -88,22 +186,121 @@ func (h *LimitBoardHandler) DownloadSecondBoardProfitCSV(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseSecondProfitQuery(c *gin.Context, includePagination bool) (secondProfitQuery, error) {
|
||||
startDate := c.DefaultQuery("tradeStartDate", c.DefaultQuery("start_date", defaultSecondProfitStartDate))
|
||||
endDate := c.DefaultQuery("tradeEndDate", c.DefaultQuery("end_date", defaultSecondProfitEndDate))
|
||||
limitUpTimeType1, limitUpTimeValue1, limitUpTimeType2, limitUpTimeValue2 := secondProfitFilterParts(c, "limitUpTimeFilter")
|
||||
openPctChgType1, openPctChgValue1, openPctChgType2, openPctChgValue2 := secondProfitFilterParts(c, "openPctChgFilter")
|
||||
req := secondProfitQuery{
|
||||
TradeStartDate: startDate,
|
||||
TradeEndDate: endDate,
|
||||
LimitUpTimeFilterType1: limitUpTimeType1,
|
||||
LimitUpTimeFilterValue1: limitUpTimeValue1,
|
||||
LimitUpTimeFilterType2: limitUpTimeType2,
|
||||
LimitUpTimeFilterValue2: limitUpTimeValue2,
|
||||
OpenPctChgFilterType1: openPctChgType1,
|
||||
OpenPctChgFilterValue1: openPctChgValue1,
|
||||
OpenPctChgFilterType2: openPctChgType2,
|
||||
OpenPctChgFilterValue2: openPctChgValue2,
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
if err := validateSecondProfitDateRange(req.TradeStartDate, req.TradeEndDate); err != nil {
|
||||
return req, err
|
||||
}
|
||||
|
||||
timeRule, err := parseSecondProfitCompareFilter(
|
||||
req.LimitUpTimeFilterType1,
|
||||
req.LimitUpTimeFilterValue1,
|
||||
req.LimitUpTimeFilterType2,
|
||||
req.LimitUpTimeFilterValue2,
|
||||
parseSecondProfitTimeValue,
|
||||
"limitUpTimeFilter",
|
||||
)
|
||||
if err != nil {
|
||||
return req, err
|
||||
}
|
||||
req.LimitUpTimeRule = timeRule
|
||||
|
||||
pctRule, err := parseSecondProfitCompareFilter(
|
||||
req.OpenPctChgFilterType1,
|
||||
req.OpenPctChgFilterValue1,
|
||||
req.OpenPctChgFilterType2,
|
||||
req.OpenPctChgFilterValue2,
|
||||
parseSecondProfitFloatValue,
|
||||
"openPctChgFilter",
|
||||
)
|
||||
if err != nil {
|
||||
return req, err
|
||||
}
|
||||
req.OpenPctChgRule = pctRule
|
||||
|
||||
if includePagination {
|
||||
pageText := strings.TrimSpace(c.DefaultQuery("page", "1"))
|
||||
page, err := strconv.Atoi(pageText)
|
||||
if err != nil || page <= 0 {
|
||||
return req, fmt.Errorf("page 必须为正整数")
|
||||
}
|
||||
req.Page = page
|
||||
|
||||
pageSizeText := strings.TrimSpace(c.DefaultQuery("pageSize", "10"))
|
||||
pageSize, err := strconv.Atoi(pageSizeText)
|
||||
if err != nil || pageSize <= 0 {
|
||||
return req, fmt.Errorf("pageSize 必须为正整数")
|
||||
}
|
||||
req.PageSize = pageSize
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func secondProfitFilterParts(c *gin.Context, base string) (string, string, string, string) {
|
||||
type1 := strings.TrimSpace(c.Query(base + "Type1"))
|
||||
value1 := strings.TrimSpace(c.Query(base + "Value1"))
|
||||
type2 := strings.TrimSpace(c.Query(base + "Type2"))
|
||||
value2 := strings.TrimSpace(c.Query(base + "Value2"))
|
||||
if type1 != "" || value1 != "" || type2 != "" || value2 != "" {
|
||||
return type1, value1, type2, value2
|
||||
}
|
||||
|
||||
values := c.QueryArray(base)
|
||||
if len(values) == 0 {
|
||||
values = c.QueryArray(base + "[]")
|
||||
}
|
||||
if len(values) > 0 {
|
||||
return secondProfitFilterArrayValue(values, 0),
|
||||
secondProfitFilterArrayValue(values, 1),
|
||||
secondProfitFilterArrayValue(values, 2),
|
||||
secondProfitFilterArrayValue(values, 3)
|
||||
}
|
||||
|
||||
return "", "", "", ""
|
||||
}
|
||||
|
||||
func secondProfitFilterArrayValue(values []string, index int) string {
|
||||
if index >= len(values) {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(values[index])
|
||||
}
|
||||
|
||||
func validateSecondProfitDateRange(startDate, endDate string) error {
|
||||
start, err := time.Parse("2006-01-02", startDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("start_date 格式必须为 YYYY-MM-DD")
|
||||
return fmt.Errorf("tradeStartDate 格式必须为 YYYY-MM-DD")
|
||||
}
|
||||
end, err := time.Parse("2006-01-02", endDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("end_date 格式必须为 YYYY-MM-DD")
|
||||
return fmt.Errorf("tradeEndDate 格式必须为 YYYY-MM-DD")
|
||||
}
|
||||
if end.Before(start) {
|
||||
return fmt.Errorf("end_date 不能早于 start_date")
|
||||
return fmt.Errorf("tradeEndDate 不能早于 tradeStartDate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *LimitBoardHandler) loadSecondProfitCSVRows(startDate, endDate string) ([]secondProfitCSVRow, error) {
|
||||
func (h *LimitBoardHandler) loadSecondProfitRows(req secondProfitQuery) ([]secondProfitCSVRow, error) {
|
||||
query := `
|
||||
WITH open_calendar AS (
|
||||
SELECT
|
||||
@@ -147,36 +344,49 @@ INNER JOIN stock_limit_combined_data pl
|
||||
LEFT JOIN stock_limit_combined_data ppl
|
||||
ON ppl.ts_code = l.ts_code AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
||||
LEFT JOIN stock_daily d
|
||||
ON d.ts_code = l.ts_code AND d.trade_date = l.trade_date
|
||||
ON l.ts_code COLLATE utf8mb4_0900_ai_ci = d.ts_code AND d.trade_date = l.trade_date
|
||||
LEFT JOIN stock_daily pd
|
||||
ON pd.ts_code = l.ts_code AND pd.trade_date = c.previous_trade_date
|
||||
ON l.ts_code COLLATE utf8mb4_0900_ai_ci = pd.ts_code AND pd.trade_date = c.previous_trade_date
|
||||
LEFT JOIN stock_daily nd
|
||||
ON nd.ts_code = l.ts_code AND nd.trade_date = c.next_trade_date
|
||||
ON l.ts_code COLLATE utf8mb4_0900_ai_ci = nd.ts_code AND nd.trade_date = c.next_trade_date
|
||||
WHERE c.trade_date BETWEEN ? AND ?
|
||||
AND c.previous_trade_date IS NOT NULL
|
||||
AND c.previous_two_trade_date IS NOT NULL
|
||||
AND ppl.ts_code IS NULL
|
||||
AND d.open IS NOT NULL
|
||||
AND COALESCE(pl.calc_close, pl.tushare_close, pd.close) > 0
|
||||
AND d.open < COALESCE(pl.calc_close, pl.tushare_close, pd.close) * 1.01
|
||||
ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
|
||||
var rawRows []secondProfitCSVRow
|
||||
if err := h.Service.DB.Raw(query, startDate, endDate).Scan(&rawRows).Error; err != nil {
|
||||
if err := h.Service.DB.Raw(query, req.TradeStartDate, req.TradeEndDate).Scan(&rawRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := make([]secondProfitCSVRow, 0, len(rawRows))
|
||||
for _, row := range rawRows {
|
||||
if !isSecondProfitBeforeEleven(row.FirstLimitTime.String) {
|
||||
if req.LimitUpTimeRule.Enabled() {
|
||||
limitTimeValue, ok := secondProfitTimeSeconds(row.FirstLimitTime.String)
|
||||
if !ok || !req.LimitUpTimeRule.Match(float64(limitTimeValue)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if row.Open.Valid && row.PreviousClose.Valid && row.PreviousClose.Float64 > 0 {
|
||||
row.OpenPreviousCloseRatio = row.Open.Float64 / row.PreviousClose.Float64
|
||||
row.OpenPctChg = sql.NullFloat64{
|
||||
Float64: secondProfitRound2((row.Open.Float64 - row.PreviousClose.Float64) / row.PreviousClose.Float64 * 100),
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if req.OpenPctChgRule.Enabled() && (!row.OpenPctChg.Valid || !req.OpenPctChgRule.Match(row.OpenPctChg.Float64)) {
|
||||
continue
|
||||
}
|
||||
row.OpenPreviousCloseRatio = row.Open / row.PreviousClose
|
||||
if row.Close > 0 {
|
||||
row.NextOpenReturnPct = secondProfitPctReturn(row.NextOpen, row.Close)
|
||||
row.NextHighReturnPct = secondProfitPctReturn(row.NextHigh, row.Close)
|
||||
row.NextCloseReturnPct = secondProfitPctReturn(row.NextClose, row.Close)
|
||||
|
||||
if row.Close.Valid && row.Close.Float64 > 0 {
|
||||
row.NextOpenReturnPct = secondProfitPctReturn(row.NextOpen, row.Close.Float64)
|
||||
row.NextHighReturnPct = secondProfitPctReturn(row.NextHigh, row.Close.Float64)
|
||||
row.NextLowReturnPct = secondProfitPctReturn(row.NextLow, row.Close.Float64)
|
||||
row.NextCloseReturnPct = secondProfitPctReturn(row.NextClose, row.Close.Float64)
|
||||
}
|
||||
row.NextOpenProfitable = secondProfitLabel(row.NextOpenReturnPct)
|
||||
row.NextCloseProfitable = secondProfitLabel(row.NextCloseReturnPct)
|
||||
row.NextIntradayProfitable = secondProfitLabel(row.NextHighReturnPct)
|
||||
rows = append(rows, row)
|
||||
@@ -186,62 +396,152 @@ ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
|
||||
func secondProfitCSVHeaders() []string {
|
||||
return []string{
|
||||
"交易日", "前一交易日", "前二交易日", "后一交易日", "股票代码", "股票简称", "市场", "行业",
|
||||
"T日开盘价", "T日收盘价", "T日涨停价", "T-1收盘价", "开盘/T-1收盘%", "首次涨停时间",
|
||||
"T日涨跌幅%", "成交额", "换手率%", "封单额", "开板次数", "涨停统计",
|
||||
"T+1开盘价", "T+1最高价", "T+1最低价", "T+1收盘价",
|
||||
"T+1开盘收益%", "T+1最高收益%", "T+1收盘收益%", "T+1收盘是否盈利", "T+1盘中是否可盈利",
|
||||
"交易日", "股票代码", "股票简称", "市场", "行业",
|
||||
"T+1开盘是否盈利", "T+1收盘是否盈利", "T+1盘中是否可盈利",
|
||||
"T+1最高盈利%", "T+1最低盈利%", "开板次数", "换手率%",
|
||||
"首次涨停时间", "封单额", "成交额",
|
||||
}
|
||||
}
|
||||
|
||||
func secondProfitCSVRecord(row secondProfitCSVRow) []string {
|
||||
return []string{
|
||||
row.TradeDate,
|
||||
row.PreviousTradeDate,
|
||||
row.PreviousTwoTradeDate,
|
||||
row.NextTradeDate,
|
||||
row.TsCode,
|
||||
row.Name,
|
||||
row.Market,
|
||||
row.Industry,
|
||||
secondProfitFormatFloat(row.Open),
|
||||
secondProfitFormatFloat(row.Close),
|
||||
secondProfitFormatFloat(row.LimitPrice),
|
||||
secondProfitFormatFloat(row.PreviousClose),
|
||||
secondProfitFormatFloat(secondProfitRound2(row.OpenPreviousCloseRatio * 100)),
|
||||
secondProfitFormatLimitTime(row.FirstLimitTime.String),
|
||||
secondProfitFormatNullFloat(row.PctChg),
|
||||
secondProfitFormatNullFloat(row.Amount),
|
||||
secondProfitFormatNullFloat(row.TurnoverRate),
|
||||
secondProfitFormatNullFloat(row.SealAmount),
|
||||
secondProfitFormatNullInt(row.BreakLimitCount),
|
||||
row.LimitUpStat,
|
||||
secondProfitFormatNullFloat(row.NextOpen),
|
||||
secondProfitFormatNullFloat(row.NextHigh),
|
||||
secondProfitFormatNullFloat(row.NextLow),
|
||||
secondProfitFormatNullFloat(row.NextClose),
|
||||
secondProfitFormatNullFloat(row.NextOpenReturnPct),
|
||||
secondProfitFormatNullFloat(row.NextHighReturnPct),
|
||||
secondProfitFormatNullFloat(row.NextCloseReturnPct),
|
||||
row.NextOpenProfitable,
|
||||
row.NextCloseProfitable,
|
||||
row.NextIntradayProfitable,
|
||||
secondProfitFormatNullFloat(row.NextHighReturnPct),
|
||||
secondProfitFormatNullFloat(row.NextLowReturnPct),
|
||||
secondProfitFormatNullInt(row.BreakLimitCount),
|
||||
secondProfitFormatNullFloat(row.TurnoverRate),
|
||||
secondProfitFormatLimitTime(row.FirstLimitTime.String),
|
||||
secondProfitFormatNullFloat(row.SealAmount),
|
||||
secondProfitFormatNullFloat(row.Amount),
|
||||
}
|
||||
}
|
||||
|
||||
func isSecondProfitBeforeEleven(limitTime string) bool {
|
||||
func secondProfitBuildItem(row secondProfitCSVRow) secondProfitListItem {
|
||||
return secondProfitListItem{
|
||||
TradeDate: row.TradeDate,
|
||||
TsCode: row.TsCode,
|
||||
Name: row.Name,
|
||||
Market: row.Market,
|
||||
Industry: row.Industry,
|
||||
NextOpenProfitable: row.NextOpenProfitable,
|
||||
NextCloseProfitable: row.NextCloseProfitable,
|
||||
NextIntradayProfitable: row.NextIntradayProfitable,
|
||||
NextHighProfitPct: secondProfitNullFloatPtr(row.NextHighReturnPct),
|
||||
NextLowProfitPct: secondProfitNullFloatPtr(row.NextLowReturnPct),
|
||||
BreakLimitCount: secondProfitNullIntPtr(row.BreakLimitCount),
|
||||
TurnoverRatePct: secondProfitNullFloatPtr(row.TurnoverRate),
|
||||
FirstLimitTime: secondProfitFormatLimitTime(row.FirstLimitTime.String),
|
||||
SealAmount: secondProfitNullFloatPtr(row.SealAmount),
|
||||
Amount: secondProfitNullFloatPtr(row.Amount),
|
||||
}
|
||||
}
|
||||
|
||||
func parseSecondProfitCompareFilter(op1, value1, op2, value2 string, parseValue func(string) (float64, error), fieldName string) (secondProfitCompareFilter, error) {
|
||||
filter := secondProfitCompareFilter{}
|
||||
if err := appendSecondProfitCompareCondition(&filter, op1, value1, parseValue, fieldName, "第一组"); err != nil {
|
||||
return filter, err
|
||||
}
|
||||
if err := appendSecondProfitCompareCondition(&filter, op2, value2, parseValue, fieldName, "第二组"); err != nil {
|
||||
return filter, err
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func appendSecondProfitCompareCondition(filter *secondProfitCompareFilter, op, rawValue string, parseValue func(string) (float64, error), fieldName, groupName string) error {
|
||||
op = strings.TrimSpace(op)
|
||||
rawValue = strings.TrimSpace(rawValue)
|
||||
if op == "" && rawValue == "" {
|
||||
return nil
|
||||
}
|
||||
if op == "" || rawValue == "" {
|
||||
return fmt.Errorf("%s %s操作符和值必须同时填写", fieldName, groupName)
|
||||
}
|
||||
if !isSecondProfitCompareOp(op) {
|
||||
return fmt.Errorf("%s %s操作符必须为 <、>、<=、>=、=", fieldName, groupName)
|
||||
}
|
||||
value, err := parseValue(rawValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s值格式错误", fieldName, groupName)
|
||||
}
|
||||
filter.Conditions = append(filter.Conditions, secondProfitCompareCondition{Op: op, Value: value})
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSecondProfitCompareOp(op string) bool {
|
||||
switch op {
|
||||
case "<", ">", "<=", ">=", "=":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (f secondProfitCompareFilter) Enabled() bool {
|
||||
return len(f.Conditions) > 0
|
||||
}
|
||||
|
||||
func (f secondProfitCompareFilter) Match(value float64) bool {
|
||||
if !f.Enabled() {
|
||||
return true
|
||||
}
|
||||
for _, condition := range f.Conditions {
|
||||
if !condition.Match(value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c secondProfitCompareCondition) Match(value float64) bool {
|
||||
switch c.Op {
|
||||
case "<":
|
||||
return value < c.Value
|
||||
case "<=":
|
||||
return value <= c.Value
|
||||
case ">":
|
||||
return value > c.Value
|
||||
case ">=":
|
||||
return value >= c.Value
|
||||
case "=":
|
||||
return value == c.Value
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseSecondProfitTimeValue(value string) (float64, error) {
|
||||
seconds, ok := secondProfitTimeSeconds(value)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid time")
|
||||
}
|
||||
return float64(seconds), nil
|
||||
}
|
||||
|
||||
func parseSecondProfitFloatValue(value string) (float64, error) {
|
||||
return strconv.ParseFloat(strings.TrimSpace(value), 64)
|
||||
}
|
||||
|
||||
func secondProfitTimeSeconds(limitTime string) (int, bool) {
|
||||
timeText := strings.TrimSpace(limitTime)
|
||||
if timeText == "" {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
|
||||
hour, minute, second := 0, 0, 0
|
||||
if strings.Contains(timeText, ":") {
|
||||
if n, _ := fmt.Sscanf(timeText, "%d:%d:%d", &hour, &minute, &second); n < 2 {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
} else {
|
||||
if len(timeText) < 4 {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
switch len(timeText) {
|
||||
case 4:
|
||||
@@ -250,26 +550,26 @@ func isSecondProfitBeforeEleven(limitTime string) bool {
|
||||
timeText = "0" + timeText
|
||||
case 6:
|
||||
default:
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
var err error
|
||||
hour, err = strconv.Atoi(timeText[0:2])
|
||||
if err != nil {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
minute, err = strconv.Atoi(timeText[2:4])
|
||||
if err != nil {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
second, err = strconv.Atoi(timeText[4:6])
|
||||
if err != nil {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
if hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 {
|
||||
return false
|
||||
return 0, false
|
||||
}
|
||||
return hour < 11
|
||||
return hour*3600 + minute*60 + second, true
|
||||
}
|
||||
|
||||
func secondProfitFormatLimitTime(value string) string {
|
||||
@@ -327,3 +627,19 @@ func secondProfitFormatNullInt(value sql.NullInt64) string {
|
||||
}
|
||||
return strconv.FormatInt(value.Int64, 10)
|
||||
}
|
||||
|
||||
func secondProfitNullFloatPtr(value sql.NullFloat64) *float64 {
|
||||
if !value.Valid {
|
||||
return nil
|
||||
}
|
||||
v := value.Float64
|
||||
return &v
|
||||
}
|
||||
|
||||
func secondProfitNullIntPtr(value sql.NullInt64) *int64 {
|
||||
if !value.Valid {
|
||||
return nil
|
||||
}
|
||||
v := value.Int64
|
||||
return &v
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user