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 # 深证ID
outer: # 外盘
- 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 {
@Autowired
private 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 {
@Autowired
private StockBusinessMapper stockBusinessMapper;
@Autowired
private StockMarketIndexInfoMapper stockMarketIndexInfoMapper;
@Autowired
private StockInfoConfig stockInfoConfig;
@Override
public List<StockBusiness> getAllStockBusiness() {
return stockBusinessMapper.findAll();
}
/**
* 获取国内大盘的实时数据
* @return
*/
@Override
public 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">
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 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 curTime
from stock_market_index_info as smi
where smi.market_code in
<foreach collection="marketIds" item="marketId" open="(" separator="," close=")">
#{marketId}
</foreach>
and smi.cur_time=#{timePoint}
</select>