Browse Source

Merge pull request #833 in DESIGN/design from ~PLOUGH/10-design:feature/10.0 to feature/10.0

* commit 'fb7c78b89f87c954cb1d14e7ba5668972a4b2962':
  REPORT-15314 103模板信息收集=>调整代码
  REPORT-15314 103模板信息收集=>考虑放假的情况
  REPORT-15314 103模板信息收集=>精确度提升
research/10.0
plough 5 years ago
parent
commit
4c962dfae6
  1. 2
      designer-base/src/main/java/com/fr/design/mainframe/DesktopCardPane.java
  2. 39
      designer-base/src/main/java/com/fr/design/mainframe/JTemplate.java
  3. 117
      designer-base/src/main/java/com/fr/design/mainframe/template/info/DesignerOpenHistory.java
  4. 10
      designer-base/src/main/java/com/fr/design/mainframe/template/info/TemplateInfo.java
  5. 56
      designer-base/src/main/java/com/fr/design/mainframe/template/info/TemplateInfoCollector.java
  6. 69
      designer-base/src/main/java/com/fr/design/mainframe/template/info/TimeConsumeTimer.java
  7. 108
      designer-base/src/test/java/com/fr/design/mainframe/template/info/DesignerOpenHistoryTest.java
  8. 16
      designer-base/src/test/java/com/fr/design/mainframe/template/info/TemplateInfoCollectorTest.java
  9. 62
      designer-base/src/test/java/com/fr/design/mainframe/template/info/TimeConsumeTimerTest.java

2
designer-base/src/main/java/com/fr/design/mainframe/DesktopCardPane.java

@ -35,6 +35,7 @@ public class DesktopCardPane extends BasicPane implements TargetModifiedListener
}
DesignerFrameFileDealerPane.getInstance().setCurrentEditingTemplate(jt);
if (component != null) {
component.onLostFocus();
remove(component);
}
add(component = jt, BorderLayout.CENTER);
@ -42,6 +43,7 @@ public class DesktopCardPane extends BasicPane implements TargetModifiedListener
repaint();
revalidate();
component.requestGridFocus();
component.onGetFocus();
}
protected JTemplate<?, ?> getSelectedJTemplate() {

39
designer-base/src/main/java/com/fr/design/mainframe/JTemplate.java

@ -36,6 +36,7 @@ import com.fr.design.i18n.Toolkit;
import com.fr.design.layout.FRGUIPaneFactory;
import com.fr.design.mainframe.template.info.TemplateInfoCollector;
import com.fr.design.mainframe.template.info.TemplateProcessInfo;
import com.fr.design.mainframe.template.info.TimeConsumeTimer;
import com.fr.design.mainframe.toolbar.ToolBarMenuDockPlus;
import com.fr.design.mainframe.toolbar.VcsScene;
import com.fr.design.menu.MenuDef;
@ -78,7 +79,6 @@ import java.util.regex.Pattern;
public abstract class JTemplate<T extends BaseBook, U extends BaseUndoState<?>> extends TargetComponent<T> implements ToolBarMenuDockPlus, DesignerProxy {
// TODO ALEX_SEP editingFILE这个属性一定要吗?如果非要不可,有没有可能保证不为null
private static final int PREFIX_NUM = 3000;
private static final int ONE_THOUSAND = 1000;
private FILE editingFILE = null;
// alex:初始状态为saved,这样不管是新建模板,还是打开模板,如果未做任何操作直接关闭,不提示保存
private boolean saved = true;
@ -92,7 +92,7 @@ public abstract class JTemplate<T extends BaseBook, U extends BaseUndoState<?>>
private static short currentIndex = 0;// 此变量用于多次新建模板时,让名字不重复
private DesignModelAdapter<T, ?> designModel;
private PreviewProvider previewType;
private long openTime = 0L; // 打开模板的时间点(包括新建模板)
private TimeConsumeTimer consumeTimer = new TimeConsumeTimer();
public int resolution = ScreenResolution.getScreenResolution();
public JTemplate() {
@ -100,7 +100,6 @@ public abstract class JTemplate<T extends BaseBook, U extends BaseUndoState<?>>
public JTemplate(T t, String defaultFileName) {
this(t, new MemFILE(newTemplateNameByIndex(defaultFileName)), true);
openTime = System.currentTimeMillis();
}
public JTemplate(T t, FILE file) {
@ -123,10 +122,24 @@ public abstract class JTemplate<T extends BaseBook, U extends BaseUndoState<?>>
this.add(createCenterPane(), BorderLayout.CENTER);
this.undoState = createUndoState();
designModel = createDesignModel();
// 如果不是新建模板,并且在收集列表中
if (!isNewFile && TemplateInfoCollector.getInstance().contains(t.getTemplateID())) {
openTime = System.currentTimeMillis();
consumeTimer.setEnabled(shouldInitForCollectInfo(isNewFile));
}
void onGetFocus() {
consumeTimer.start();
}
void onLostFocus() {
consumeTimer.stop();
}
private boolean shouldInitForCollectInfo(boolean isNewFile) {
if (isNewFile) {
return true;
}
// 不是新建模板,但是已经在收集列表中
return TemplateInfoCollector.getInstance().contains(template.getTemplateID());
}
// 刷新右侧属性面板
@ -139,9 +152,8 @@ public abstract class JTemplate<T extends BaseBook, U extends BaseUndoState<?>>
// 为收集模版信息作准备
private void initForCollect() {
generateTemplateId();
if (openTime == 0) {
openTime = System.currentTimeMillis();
}
consumeTimer.setEnabled(true);
consumeTimer.start();
}
private void collectInfo() { // 执行收集操作
@ -149,16 +161,15 @@ public abstract class JTemplate<T extends BaseBook, U extends BaseUndoState<?>>
}
private void collectInfo(String originID) { // 执行收集操作
if (openTime == 0) { // 旧模板,不收集数据
if (!consumeTimer.isEnabled()) {
return;
}
long saveTime = System.currentTimeMillis(); // 保存模板的时间点
try {
long timeConsume = ((saveTime - openTime) / ONE_THOUSAND); // 制作模板耗时(单位:s)
TemplateInfoCollector.getInstance().collectInfo(template.getTemplateID(), originID, getProcessInfo(), (int)timeConsume);
int timeConsume = consumeTimer.popTime();
TemplateInfoCollector.getInstance().collectInfo(template.getTemplateID(), originID, getProcessInfo(), timeConsume);
} catch (Throwable th) { // 不管收集过程中出现任何异常,都不应该影响模版保存
}
openTime = saveTime; // 更新 openTime,准备下一次计算
consumeTimer.start(); // 准备下一次计算
}
public abstract TemplateProcessInfo<T> getProcessInfo();

117
designer-base/src/main/java/com/fr/design/mainframe/template/info/DesignerOpenHistory.java

@ -0,0 +1,117 @@
package com.fr.design.mainframe.template.info;
import com.fr.general.ComparatorUtils;
import com.fr.log.FineLoggerFactory;
import com.fr.stable.StringUtils;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLReadable;
import com.fr.stable.xml.XMLWriter;
import com.fr.stable.xml.XMLableReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 管理打开设计器的日期记录
* Created by plough on 2019/4/19.
*/
class DesignerOpenHistory implements XMLReadable, XMLWriter {
static final String XML_TAG = "DesignerOpenHistory";
private static DesignerOpenHistory singleton;
private static final String SPLITTER = ",";
private static final int LENGTH = 3; // 保留最近 3 次的记录
// 最近的日期是 history[0],最早的日期是 history[LENGTH-1]
private String[] history = new String[LENGTH];
private DesignerOpenHistory() {
for (int i = 0; i < LENGTH; i++) {
history[i] = StringUtils.EMPTY;
}
}
static DesignerOpenHistory getInstance() {
if (singleton == null) {
singleton = new DesignerOpenHistory();
}
return singleton;
}
void update() {
String today = getToday();
if (ComparatorUtils.equals(history[0], today)) {
return;
}
shiftByOne();
history[0] = today;
}
/**
* 获取历史记录中囊括的日子数即最早的历史记录 history[LENGTH - 1]到最晚的记录 history[0] 之间的时间跨度
*/
int getHistorySpanDayCount() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
Date earliestDate = sdf.parse(getEarliestDate());
Date latestDate = sdf.parse(history[0]);
long diffInMillies = latestDate.getTime() - earliestDate.getTime();
return (int)TimeUnit.DAYS.convert(diffInMillies,TimeUnit.MILLISECONDS) + 1;
} catch (ParseException e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
}
return 1;
}
/**
* 今天是否已经打开过设计器
*/
boolean hasOpenedToday() {
String today = getToday();
return ComparatorUtils.equals(today, history[0]);
}
private String getEarliestDate() {
for (int i = LENGTH - 1; i >= 0; i--) {
if (StringUtils.isNotEmpty(history[i])) {
return history[i];
}
}
throw new AssertionError("Designer open history is empty!");
}
private void shiftByOne() {
for (int i = LENGTH - 1; i > 0; i--) {
history[i] = history[i-1];
}
}
private String getToday() {
return new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
}
@Override
public String toString() {
return StringUtils.join(SPLITTER, history);
}
private void parseString(String s) {
String[] arr = s.split(SPLITTER);
System.arraycopy(arr, 0, history, 0, arr.length);
}
@Override
public void readXML(XMLableReader reader) {
if (XML_TAG.equals(reader.getTagName())) {
parseString(reader.getElementValue());
}
}
@Override
public void writeXML(XMLPrintWriter writer) {
writer.startTAG(XML_TAG);
writer.textNode(toString());
writer.end();
}
}

10
designer-base/src/main/java/com/fr/design/mainframe/template/info/TemplateInfo.java

@ -203,7 +203,11 @@ class TemplateInfo implements XMLReadable, XMLWriter {
}
boolean isComplete() {
return idleDayCount > COMPLETE_DAY_COUNT;
// 条件 1. 超过15天未编辑
// 条件 2. 设计器在这段未编辑的时间内启动超过 X 次(目前定的 X = 3)。即"设计器最近 X 次启动的时间跨度" < "未编辑时间";
return idleDayCount > COMPLETE_DAY_COUNT
&& DesignerOpenHistory.getInstance().getHistorySpanDayCount() < idleDayCount;
}
String getConsumingMapJsonString() {
@ -245,4 +249,8 @@ class TemplateInfo implements XMLReadable, XMLWriter {
void addIdleDayCountByOne() {
this.idleDayCount += 1;
}
int getIdleDayCount() {
return this.idleDayCount;
}
}

56
designer-base/src/main/java/com/fr/design/mainframe/template/info/TemplateInfoCollector.java

@ -2,7 +2,6 @@ package com.fr.design.mainframe.template.info;
import com.fr.base.FRContext;
import com.fr.design.DesignerEnvManager;
import com.fr.general.ComparatorUtils;
import com.fr.log.FineLoggerFactory;
import com.fr.stable.ProductConstants;
import com.fr.stable.StableUtils;
@ -19,9 +18,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
@ -31,17 +28,23 @@ import java.util.Map;
*/
public class TemplateInfoCollector implements XMLReadable, XMLWriter {
private static final String XML_TAG = "TplInfo";
private static final String XML_DESIGNER_OPEN_DATE = "DesignerOpenDate";
private static final String XML_TEMPLATE_INFO_LIST = "TemplateInfoList";
private static final String XML_FILE_NAME = "tpl.info";
private static TemplateInfoCollector instance;
private Map<String, TemplateInfo> templateInfoMap;
private String designerOpenDate; //设计器最近一次打开日期
private DesignerOpenHistory designerOpenHistory;
private TemplateInfoCollector() {
init();
}
private void init() {
templateInfoMap = new HashMap<>();
designerOpenHistory = DesignerOpenHistory.getInstance();
loadFromFile();
}
public static TemplateInfoCollector getInstance() {
if (instance == null) {
instance = new TemplateInfoCollector();
@ -96,6 +99,7 @@ public class TemplateInfoCollector implements XMLReadable, XMLWriter {
* 发送本地模板信息到服务器并清空已发送模版的本地记录
*/
public void sendTemplateInfo() {
// 每次启动设计器后,都会执行这个函数(被 InformationCollector 的 collectStartTime 调用)
addIdleDayCount();
removeTestTemplates();
@ -120,13 +124,6 @@ public class TemplateInfoCollector implements XMLReadable, XMLWriter {
return new File(StableUtils.pathJoin(ProductConstants.getEnvHome(), XML_FILE_NAME));
}
private void init() {
templateInfoMap = new HashMap<>();
setDesignerOpenDate();
loadFromFile();
}
void loadFromFile() {
if (!getInfoFile().exists()) {
return;
@ -150,21 +147,6 @@ public class TemplateInfoCollector implements XMLReadable, XMLWriter {
return templateInfo;
}
/**
* 把设计器最近打开日期设定为当前日期
*/
private void setDesignerOpenDate() {
designerOpenDate = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
}
/**
* 判断今天是否第一次打开设计器为了防止同一天内多次 addIdleDayCount
*/
private boolean designerOpenFirstTime() {
String today = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
return !ComparatorUtils.equals(today, designerOpenDate);
}
private boolean shouldCollectInfo() {
//只收集本地环境的
if (!WorkContext.getCurrent().isLocal()) {
@ -189,12 +171,14 @@ public class TemplateInfoCollector implements XMLReadable, XMLWriter {
* 更新 day_count打开设计器却未编辑模板的连续日子
*/
private void addIdleDayCount() {
if (designerOpenFirstTime()) {
for (TemplateInfo templateInfo : templateInfoMap.values()) {
templateInfo.addIdleDayCountByOne();
}
setDesignerOpenDate();
// 判断今天是否第一次打开设计器,为了防止同一天内,多次 addIdleDayCount
if (designerOpenHistory.hasOpenedToday()) {
return;
}
for (TemplateInfo templateInfo : templateInfoMap.values()) {
templateInfo.addIdleDayCountByOne();
}
designerOpenHistory.update();
}
// 删除所有已完成的测试模版
@ -220,8 +204,8 @@ public class TemplateInfoCollector implements XMLReadable, XMLWriter {
if (reader.isChildNode()) {
try {
String name = reader.getTagName();
if (XML_DESIGNER_OPEN_DATE.equals(name)) {
this.designerOpenDate = reader.getElementValue();
if (DesignerOpenHistory.XML_TAG.equals(name)) {
reader.readXMLObject(designerOpenHistory);
} else if (TemplateInfo.XML_TAG.equals(name)) {
TemplateInfo templateInfo = TemplateInfo.newInstanceByRead(reader);
templateInfoMap.put(templateInfo.getTemplateID(), templateInfo);
@ -236,9 +220,7 @@ public class TemplateInfoCollector implements XMLReadable, XMLWriter {
public void writeXML(XMLPrintWriter writer) {
writer.startTAG(XML_TAG);
writer.startTAG(XML_DESIGNER_OPEN_DATE);
writer.textNode(designerOpenDate);
writer.end();
designerOpenHistory.writeXML(writer);
writer.startTAG(XML_TEMPLATE_INFO_LIST);
for (TemplateInfo templateInfo : templateInfoMap.values()) {

69
designer-base/src/main/java/com/fr/design/mainframe/template/info/TimeConsumeTimer.java

@ -0,0 +1,69 @@
package com.fr.design.mainframe.template.info;
/**
* Created by plough on 2019/4/19.
*/
public class TimeConsumeTimer {
private static final int ONE_THOUSAND = 1000;
private enum State {
RUNNING, STOPPED
}
private int timeConsume; // 单位 s
private long startMS; // 单位 ms
private long stopMS;
private State state;
private boolean enabled;
public TimeConsumeTimer() {
reset();
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void start() {
if (!isEnabled() || isRunning()) {
return;
}
startMS = System.currentTimeMillis();
state = State.RUNNING;
}
public void stop() {
if (!isEnabled() || !isRunning()) {
return;
}
stopMS = System.currentTimeMillis();
timeConsume += ((stopMS - startMS) / ONE_THOUSAND);
startMS = 0;
stopMS = 0;
state = State.STOPPED;
}
public int popTime() {
if (!isEnabled()) {
return 0;
}
stop();
int result = timeConsume;
reset();
return result;
}
private boolean isRunning() {
return state == State.RUNNING;
}
private void reset() {
timeConsume = 0;
startMS = 0;
stopMS = 0;
state = State.STOPPED;
}
}

108
designer-base/src/test/java/com/fr/design/mainframe/template/info/DesignerOpenHistoryTest.java

@ -0,0 +1,108 @@
package com.fr.design.mainframe.template.info;
import com.fr.invoke.Reflect;
import com.fr.stable.StringUtils;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLableReader;
import com.fr.third.javax.xml.stream.XMLStreamException;
import org.junit.Before;
import org.junit.Test;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Created by plough on 2019/4/21.
*/
public class DesignerOpenHistoryTest {
private DesignerOpenHistory openHistory;
private String[] mockHistory = new String[] {
"2019-04-08", "2019-04-03", "2019-03-29"
};
@Before
public void setUp() {
openHistory = DesignerOpenHistory.getInstance();
Reflect.on(openHistory).set("history", mockHistory);
}
@Test
public void testReadAndWrite() throws XMLStreamException {
// 写入 xml
StringWriter sw = new StringWriter();
XMLPrintWriter writer = XMLPrintWriter.create(new PrintWriter(sw));
openHistory.writeXML(writer);
writer.flush();
writer.close();
String xmlText = sw.getBuffer().toString();
// 临时修改配置
Reflect.on(openHistory).set("history", new String[] {"", "", ""});
// 从 xml 中读取
StringReader sr = new StringReader(xmlText);
XMLableReader xmlReader = XMLableReader.createXMLableReader(sr);
xmlReader.readXMLObject(openHistory);
// 验证:与写入时的配置一致
assertArrayEquals(mockHistory, (String[])Reflect.on(openHistory).field("history").get());
}
@Test
public void testToString() {
assertEquals("2019-04-08,2019-04-03,2019-03-29", openHistory.toString());
}
@Test
public void testParseString() {
String[] mockDates = {"2020-04-08", "2019-04-03", "2016-03-29"};
Reflect.on(openHistory).call("parseString", StringUtils.join(",", mockDates));
assertArrayEquals(mockDates, (String[])Reflect.on(openHistory).field("history").get());
}
@Test
public void testGetHistorySpanDayCount() {
assertEquals(11, openHistory.getHistorySpanDayCount());
Reflect.on(openHistory).set("history", new String[] {"2019-05-03", "2019-05-02", ""});
assertEquals(2, openHistory.getHistorySpanDayCount());
Reflect.on(openHistory).set("history", new String[] {"2019-05-03", "", ""});
assertEquals(1, openHistory.getHistorySpanDayCount());
try {
Reflect.on(openHistory).set("history", new String[] {"", "", ""});
fail("should not be here");
} catch (AssertionError ignore) {
}
}
@Test
public void testHasOpenedToday() {
assertFalse(openHistory.hasOpenedToday());
Reflect.on(openHistory).set("history", new String[] {getToday(), "2019-02-02", ""});
assertTrue(openHistory.hasOpenedToday());
}
@Test
public void testUpdate() {
openHistory.update();
String[] arr = { getToday(), "2019-04-08", "2019-04-03" };
assertArrayEquals(arr, (String[])Reflect.on(openHistory).field("history").get());
}
private String getToday() {
return new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
}
}

16
designer-base/src/test/java/com/fr/design/mainframe/template/info/TemplateInfoCollectorTest.java

@ -150,4 +150,20 @@ public class TemplateInfoCollectorTest {
assertEquals(329, consumingMap.get("time_consume"));
assertEquals(129, consumingMap.get("originTime"));
}
@Test
public void testAddIdleDateCount() {
String templateID = "16a988ce-8529-42f5-b17c-2ee849355071";
TemplateInfoCollector collecter = TemplateInfoCollector.getInstance();
TemplateInfo templateInfo = collecter.getOrCreateTemplateInfoByID(templateID);
assertEquals(9, templateInfo.getIdleDayCount());
Reflect.on(collecter).call("addIdleDayCount");
assertEquals(10, templateInfo.getIdleDayCount());
// 同一天内多次调用无效
Reflect.on(collecter).call("addIdleDayCount");
assertEquals(10, templateInfo.getIdleDayCount());
}
}

62
designer-base/src/test/java/com/fr/design/mainframe/template/info/TimeConsumeTimerTest.java

@ -0,0 +1,62 @@
package com.fr.design.mainframe.template.info;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Created by plough on 2019/4/19.
*/
public class TimeConsumeTimerTest {
@Test
public void testNotEnabled() throws InterruptedException {
TimeConsumeTimer consumeTimer = new TimeConsumeTimer();
consumeTimer.start();
Thread.sleep(1100);
consumeTimer.stop();
assertEquals(0, consumeTimer.popTime());
}
@Test
public void testEnabled() throws InterruptedException {
TimeConsumeTimer consumeTimer = new TimeConsumeTimer();
consumeTimer.setEnabled(true);
consumeTimer.start();
Thread.sleep(1100);
consumeTimer.stop();
assertEquals(1, consumeTimer.popTime());
assertEquals(0, consumeTimer.popTime());
}
@Test
public void testMultiTimes() throws InterruptedException {
TimeConsumeTimer consumeTimer = new TimeConsumeTimer();
consumeTimer.setEnabled(true);
consumeTimer.start();
Thread.sleep(1010);
consumeTimer.stop();
Thread.sleep(2000);
consumeTimer.start();
Thread.sleep(1010);
assertEquals(2, consumeTimer.popTime());
}
@Test
public void testStartMultiTime() throws InterruptedException {
TimeConsumeTimer consumeTimer = new TimeConsumeTimer();
consumeTimer.setEnabled(true);
consumeTimer.start();
Thread.sleep(1010);
consumeTimer.start();
Thread.sleep(1010);
consumeTimer.start();
Thread.sleep(1010);
assertEquals(3, consumeTimer.popTime());
}
}
Loading…
Cancel
Save