今日指数项目之大盘指数功能实现
1、国内大盘指数功能
1.1国内大盘指数业务分析
1.1.1 页面原型效果
查询A股大盘最新的数据:
国内大盘数据包含:大盘代码、大盘名称、开盘点、最新点、前收盘点、交易量、交易金额、涨跌值、涨幅、振幅、当前日期
1.1.2 相关表结构分析
大盘指数包含国内和国外的大盘数据,目前我们先完成国内大盘信数据的展示功能;
国内股票大盘数据详情表设计如下:
注意事项:
数据库字段类型decimal—>java中的BigDecimal
数据库字段类型bigint—> java中的Long类型
1.1.3 A股大盘指数接口说明
功能说明:
- 获取最新国内A股大盘信息(仅包含上证和深证大盘数据);
- 查询时间点不在正常股票交易时间内,则显示最近时间点的交易信息;
- 比如:当前查询时间点是周一上午8点整,因为当天尚未开盘,则显示上周五最新的数据,也就是收盘时数据;
请求路径:/api/quot/index/all
请求方式:GET
参数:无
响应数据格式:
{"code": 1,"data": [{"code": "sh000001",//大盘编码"name": "上证指数",//指数名称"openPoint": 3267.81,//开盘点"curPoint": 3236.70,//当前点"preClosePoint": 3283.43,//前收盘点"tradeAmt": 160591,//交易量"tradeVol": 1741099,//交易金额"upDown": -46.73,//涨跌值"rose": -0.01.42,//涨幅"amplitude": 0.0164,//振幅"curTime": "2022-01-02 01:32"//当前时间},{......}]
}
A股大盘开盘周期:周一至周五,每天上午9:30到11:30和下午13:00到15:00;
1.1.4 响应结果实体类封装
我们约定从数据库查询的数据如果来自多张表或者单表的部分字段,则封装到domain实体类下;
domain、pojo、entity、vo类等实体类作为公共资源都维护在stock_common工程下;
package com.itheima.stock.pojo.domain;import lombok.Data;import java.math.BigDecimal;/*** @author by itheima* @Date 2022/1/9* @Description 定义封装多内大盘数据的实体类*/
@Data
public class InnerMarketDomain {/*** 大盘编码*/private String code;/*** 大盘名称*/private String name;/*** 开盘点*/private BigDecimal openPoint;/*** 当前点*/private BigDecimal curPoint;/*** 前收盘点*/private BigDecimal preClosePoint;/*** 交易量*/private Long tradeAmt;/*** 交易金额*/private Long tradeVol;/*** 涨跌值*/private BigDecimal upDown;/*** 涨幅*/private BigDecimal rose;/*** 振幅*/private BigDecimal amplitude;/*** 当前时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm")private Date curTime;
}
注意:在stock_common工程下直接导入day02\资料\domain\InnerMarketDomain.java 即可
1.2 国内大盘功能实现准备
1.2.1 股票交易时间工具类封装
项目中经常需要查询股票最近的一次交易时间点,而大盘的开盘时间又分为不同的时间段,这给我们的逻辑判断增加了复杂度,而且项目中股票是每分钟采集一次,时间需要精确到分钟,综上,我们需要在stock_common工程下维护一个公共的时间工具类:
package com.itheima.stock.utils;import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;/*** @author by itheima* @Date 2021/12/31* @Description 日期时间工具类*/
public class DateTimeUtil {/*** 获取指定日期下股票的上一个有效交易日时间* @return*/public static DateTime getPreviousTradingDay(DateTime dateTime){//获取指定日期对应的工作日int weekNum = dateTime.dayOfWeek().get();//判断所属工作日DateTime preDateTime=null;//周一,那么T-1就是周五if (weekNum==1){//日期后退3天preDateTime=dateTime.minusDays(3);}//周末,那么T-1就是周五else if (weekNum==7){preDateTime=dateTime.minusDays(2);}else {preDateTime=dateTime.minusDays(1);}return getDateTimeWithoutSecond(preDateTime);}/*** 判断是否是工作日* @return true:在工作日 false:不在工作日*/public static boolean isWorkDay(DateTime dateTime){//获取工作日int weekNum = dateTime.dayOfWeek().get();return weekNum>=1 && weekNum<=5;}/*** 获取上一天日期* @param dateTime* @return*/public static DateTime getPreDateTime(DateTime dateTime){return dateTime.minusDays(1);}/*** 日期转String* @param dateTime 日期* @param pattern 日期正则格式* @return*/public static String parseToString(DateTime dateTime,String pattern){return dateTime.toString(DateTimeFormat.forPattern(pattern));}/*** 获取股票日期格式字符串* @param dateTime* @return*/public static String parseToString4Stock(DateTime dateTime){return parseToString(dateTime,"yyyyMMddHHmmss");}/*** 获取指定日期的收盘日期* @param dateTime* @return*/public static DateTime getCloseDate(DateTime dateTime){return dateTime.withHourOfDay(14).withMinuteOfHour(58).withSecondOfMinute(0).withMillisOfSecond(0);}/*** 获取指定日期的开盘日期* @param dateTime* @return*/public static DateTime getOpenDate(DateTime dateTime){return dateTime.withHourOfDay(9).withMinuteOfHour(30).withSecondOfMinute(0).withMillisOfSecond(0);}/*** 获取最近的股票有效时间,精确到分钟* @param target* @return*/public static String getLastDateString4Stock(DateTime target){DateTime dateTime = getLastDate4Stock(target);dateTime=getDateTimeWithoutSecond(dateTime);return parseToString4Stock(dateTime);}/*** 获取最近的股票有效时间,精确到分钟* @param target* @return*/public static DateTime getLastDate4Stock(DateTime target){//判断是否是工作日if (isWorkDay(target)) {//当前日期开盘前if (target.isBefore(getOpenDate(target))) {target=getCloseDate(getPreviousTradingDay(target));}else if (isMarketOffTime(target)){target=target.withHourOfDay(11).withMinuteOfHour(28).withSecondOfMinute(0).withMillisOfSecond(0);}else if (target.isAfter(getCloseDate(target))){//当前日期收盘后target=getCloseDate(target);}}else{//非工作日target=getCloseDate(getPreviousTradingDay(target));}target = getDateTimeWithoutSecond(target);return target;}/*** 判断当前时间是否在大盘的中午休盘时间段* @return*/public static boolean isMarketOffTime(DateTime target){//上午休盘开始时间DateTime start = target.withHourOfDay(11).withMinuteOfHour(28).withSecondOfMinute(0).withMillisOfSecond(0);//下午开盘时间DateTime end = target.withHourOfDay(13).withMinuteOfHour(0).withSecondOfMinute(0).withMillisOfSecond(0);if (target.isAfter(start) && target.isBefore(end)) {return true;}return false;}/*** 将秒时归零* @param dateTime 指定日期* @return*/public static DateTime getDateTimeWithoutSecond(DateTime dateTime){DateTime newDate = dateTime.withSecondOfMinute(0).withMillisOfSecond(0);return newDate;}/*** 将秒时归零* @param dateTime 指定日期字符串,格式必须是:yyyy-MM-dd HH:mm:ss* @return*/public static DateTime getDateTimeWithoutSecond(String dateTime){DateTime parse = DateTime.parse(dateTime, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));return getDateTimeWithoutSecond(parse);}
}
说明:
在stock_common下直接导入日期工具类:今日指数\day02\资料\date工具类\DateTimeUtil.java
工具类借助jode-time日期插件实现,jode-date核心方式参考:day02\资料\date工具类\TestJodeDate.java
1.2.2 常量数据封装
股票常用的公共参数非常多,我们可以在stock_common下把他们封装到一个Value Object(vo)对象下,并通过Spring为调用方动态赋值;
本小节我们把股票大盘编码信息配置到StockInfoConfig实体类下:
package com.itheima.stock.pojo.vo;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;/*** @author by itheima* @Date 2021/12/30* @Description*/
@ConfigurationProperties(prefix = "stock")
@Data
public class StockInfoConfig {//A股大盘ID集合private List<String> inner;//外盘ID集合private List<String> outer;
}
在调用方stock_backend工程下定义application-stock.yml文件,并配置A股大盘和外盘的编码数据:
# 配置股票相关的参数
stock:inner: # A股- sh000001 # 上证ID- sz399001 # 深证IDouter: # 外盘- int_dji # 道琼斯- int_nasdaq # 纳斯达克- int_hangseng # 恒生- int_nikkei # 日经指数- b_FSSTI # 新加坡
同时在主配置文件application.yml中激活该配置:
spring:profiles:active: stock
说明:将股票相关的配置文件独立出来,方便后期维护,且避免产生臃肿的主配置文件;
在公共配置类中加载实体VO对象:
@EnableConfigurationProperties(StockInfoConfig.class)
@Configuration
public class CommonConfig {//省略N行
}
1.3 国内大盘指数SQL分析
业务功能:获取最新的国内大盘的数据信息
-- 功能说明:获取最新国内A股大盘信息(上证和深证)
-- 如果不在股票交易时间,则显示最近时间点的交易信息
-- 分析:就是根据大盘的编码查询大盘的最新交易数据
-- 大盘编码:sh000001 sz399001
SELECT smi.market_code AS code,smi.market_name AS name,smi.open_point AS openPoint,smi.cur_point AS curPoint,smi.pre_close_point AS preClosePoint,smi.trade_amount AS tradeAmt,smi.trade_volume AS tradeVol,smi.cur_point-smi.pre_close_point AS upDown,(smi.cur_point-smi.pre_close_point)/smi.pre_close_point AS rose,(smi.max_point-smi.min_point)/smi.pre_close_point AS amplitude,smi.cur_time AS curTime
FROM stock_market_index_info AS smi
WHERE smi.market_code IN ('sh000001','sz399001')
ORDER BY smi.cur_time DESC LIMIT 2;
# 存在的问题:1.全表查询,效率较低,如何优化?
# 一方面为了为了避免重复数据,将时间和大盘编码作为联合唯一索引,起到
-- 唯一约束的作用 另外,借助这个索引,也避免全表查询
-- 查询最新的数据,可转化成查询最新的股票交易时间点下的数据
SELECT smi.market_code AS code,smi.market_name AS name,smi.open_point AS openPoint,smi.cur_point AS curPoint,smi.pre_close_point AS preClosePoint,smi.trade_amount AS tradeAmt,smi.trade_volume AS tradeVol,smi.cur_point-smi.pre_close_point AS upDown,(smi.cur_point-smi.pre_close_point)/smi.pre_close_point AS rose,(smi.max_point-smi.min_point)/smi.pre_close_point AS amplitude,smi.cur_time AS curTime
FROM stock_market_index_info AS smi
WHERE smi.market_code IN ('sh000001','sz399001')
AND smi.cur_time ='2021-12-28 09:31:00';
# 在sql查询时,尽量避免全表查询,否则随着数据量的增加,全表查询导致的查询时间成本会不断上升!
1.4 国内大盘指数功能实现
1.4.1 定义获取A股大盘数据接口
package com.itheima.stock.controller;import com.itheima.stock.pojo.StockBusiness;
import com.itheima.stock.service.StockService;
import com.itheima.stock.vo.resp.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Map;/*** @author by itheima* @Date 2021/12/19* @Description*/
@RestController
@RequestMapping("/api/quot")
public class StockController {@Autowiredprivate StockService stockService;//其它省略...../*** 获取国内最新大盘指数* @return*/@GetMapping("/index/all")public R<List<InnerMarketDomain>> innerIndexAll(){return stockService.innerIndexAll();}
}
1.4.2 定义国内大盘数据服务
服务接口:
package com.itheima.stock.service;
import com.itheima.stock.pojo.StockBusiness;
import com.itheima.stock.vo.resp.R;import java.util.List;
import java.util.Map;/*** @author by itheima* @Date 2021/12/19* @Description 定义股票服务接口*/
public interface StockService {//其它省略....../*** 获取国内大盘的实时数据* @return*/R<List<InnerMarketDomain>> innerIndexAll();
}
服务接口实现:
package com.itheima.stock.service.impl;import com.itheima.stock.common.domain.StockInfoConfig;
import com.itheima.stock.mapper.StockBusinessMapper;
import com.itheima.stock.mapper.StockMarketIndexInfoMapper;
import com.itheima.stock.pojo.StockBusiness;
import com.itheima.stock.service.StockService;
import com.itheima.stock.vo.resp.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import java.util.List;
import java.util.Map;/*** @author by itheima* @Date 2021/12/19* @Description*/
@Service("stockService")
public class StockServiceImpl implements StockService {@Autowiredprivate StockBusinessMapper stockBusinessMapper;@Autowiredprivate StockMarketIndexInfoMapper stockMarketIndexInfoMapper;@Autowiredprivate StockInfoConfig stockInfoConfig;@Overridepublic List<StockBusiness> getAllStockBusiness() {return stockBusinessMapper.findAll();}/*** 获取国内大盘的实时数据* @return*/@Overridepublic R<List<InnerMarketDomain>> innerIndexAll() {//1.获取国内A股大盘的id集合List<String> inners = stockInfoConfig.getInner();//2.获取最近股票交易日期Date lastDate = DateTimeUtil.getLastDate4Stock(DateTime.now()).toDate();//TODO mock测试数据,后期数据通过第三方接口动态获取实时数据 可删除lastDate=DateTime.parse("2022-01-02 09:32:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toDate();//3.将获取的java Date传入接口List<InnerMarketDomain> list= stockMarketIndexInfoMapper.getMarketInfo(inners,lastDate);//4.返回查询结果return R.ok(list);}
}
1.4.3 定义mapper接口方法和xml
mapper下定义接口方法和xml:
/*** 根据大盘的id和时间查询大盘信息* @param marketIds 大盘id集合* @param timePoint 当前时间点(默认精确到分钟)* @return*/List<InnerMarketDomain> getMarketInfo(@Param("marketIds") List<String> marketIds, @Param("timePoint") Date timePoint);
定义mapper接口绑定SQL:
<select id="getMarketInfo" resultType="com.itheima.stock.pojo.domain.InnerMarketDomain">selectsmi.market_code as code,smi.market_name as name,smi.open_point as openPoint,smi.cur_point as curPoint,smi.pre_close_point as preClosePrice,smi.trade_amount as tradeAmt,smi.trade_volume as tradeVol,smi.cur_point-smi.pre_close_point as upDown,(smi.cur_point-smi.pre_close_point)/smi.pre_close_point as rose,(smi.max_point-smi.min_point)/smi.pre_close_point as amplitude,smi.cur_time as curTimefrom stock_market_index_info as smiwhere smi.market_code in<foreach collection="marketIds" item="marketId" open="(" separator="," close=")">#{marketId}</foreach>and smi.cur_time=#{timePoint}</select>