From d2a65f0c13df21354ac83bf13b15f46937bd8ecb Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Mon, 05 Jan 2026 14:07:35 +0800
Subject: [PATCH] 将定时任务迁移到定时模块中
---
ard-quartz/pom.xml | 4
ard-device/src/main/java/com/ard/device/channel/service/impl/ArdChannelServiceImpl.java | 106 -----------
ard-quartz/src/main/java/com/ard/quartz/task/DeviceTask.java | 258 ++++++++++++++++++++++++++++
ard-vtdu/src/main/java/com/ard/vtdu/service/impl/ArdVtduServiceImpl.java | 8
ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java | 27 ---
ard-device/src/main/java/com/ard/device/camera/service/impl/ArdCameraServiceImpl.java | 83 ---------
6 files changed, 263 insertions(+), 223 deletions(-)
diff --git a/ard-device/src/main/java/com/ard/device/camera/service/impl/ArdCameraServiceImpl.java b/ard-device/src/main/java/com/ard/device/camera/service/impl/ArdCameraServiceImpl.java
index 993e729..e55b8cc 100644
--- a/ard-device/src/main/java/com/ard/device/camera/service/impl/ArdCameraServiceImpl.java
+++ b/ard-device/src/main/java/com/ard/device/camera/service/impl/ArdCameraServiceImpl.java
@@ -373,87 +373,4 @@
redisService.setCacheObject(getCacheKey(camera.getId()), camera);
}
- /**
- * 同步设备通道与本地通道库信息
- *
- * @author 刘苏义
- * @date 2024/8/22 14:37
- */
- @Scheduled(fixedDelay = 5000)
- public void syncChannel() {
- try {
- log.debug("定时执行同步通道任务");
- List<ArdCamera> ardCameraList = this.selectArdCameraList(new ArdCamera());
- if (!ardCameraList.isEmpty()) {
- ardCameraList
- .forEach(ardCamera -> {
- //SDK获取实际通道
- List<ArdChannel> ardChannelListSDK = cameraSDKService.getChannels(ardCamera);
- //数据库获取通道
- ArdChannel ardChannel = new ArdChannel();
- ardChannel.setDeviceId(ardCamera.getId());
- List<ArdChannel> ardChannelListDb = ardChannelService.selectArdChannelList(ardChannel);
- //同步通道
- asyncChannel(ardChannelListDb, ardChannelListSDK);
- });
- }
- } catch (Exception ex) {
- log.error("定时执行同步通道任务异常:{}", ex.getMessage());
- }
- }
-
- /**
- * 定时执行登录失败相机重新登录
- *
- * @author 刘苏义
- * @date 2024/8/22 14:37
- */
- @Scheduled(fixedDelay = 10000)
- public void failedReconnect() {
- try {
- log.debug("定时执行失败相机重连任务");
- // 从数据库取全部设备
- List<ArdCamera> cameraList = selectArdCameraList(new ArdCamera());
- cameraList.stream()
- // 只处理登录失败或离线的设备
- .filter(camera -> camera.getLoginId() == null || camera.getLoginId() < 0 || "0".equals(camera.getState()))
- .forEach(camera -> {
- log.info("尝试重新登录设备 {}:{}", camera.getIp(), camera.getPort());
- cameraSDKService.login(camera);
- });
-
- } catch (Exception ex) {
- log.error("定时执行失败相机重连任务异常:{}", ex.getMessage());
- }
- }
-
- /**
- * 同步相机在线状态
- * 刘苏义
- * 2023/10/8 14:54:41
- */
- @Scheduled(fixedDelay = 5000)
- public void cameraState() {
- try {
- log.debug("定时执行同步相机在线状态任务");
- //调用查询方法
- List<ArdCamera> ardCameraList = selectArdCameraList(new ArdCamera());
- ardCameraList
- .forEach(camera -> {
- boolean onLineNew = cameraSDKService.isOnLine(camera);
- boolean onLineOld = "1".equals(camera.getState());
- if (onLineNew != onLineOld) {
- if (onLineNew) {
- camera.setState("1");
- } else {
- camera.setState("0");
- }
- camera.setUpdateTime(DateUtils.getNowDate());
- ardCameraMapper.updateArdCamera(camera);
- }
- });
- } catch (Exception ex) {
- log.error("同步相机在线状态异常:{}", ex.getMessage());
- }
- }
}
\ No newline at end of file
diff --git a/ard-device/src/main/java/com/ard/device/channel/service/impl/ArdChannelServiceImpl.java b/ard-device/src/main/java/com/ard/device/channel/service/impl/ArdChannelServiceImpl.java
index de0230f..6c00d76 100644
--- a/ard-device/src/main/java/com/ard/device/channel/service/impl/ArdChannelServiceImpl.java
+++ b/ard-device/src/main/java/com/ard/device/channel/service/impl/ArdChannelServiceImpl.java
@@ -30,16 +30,9 @@
@Slf4j
@Service
public class ArdChannelServiceImpl implements IArdChannelService {
- // @Resource
- //private IArdCameraService ardCameraService;
+
@Resource
private ArdChannelMapper ardChannelMapper;
- @Resource
- private IArdVtduService ardVtduService;
- @Resource
- private MediamtxConfig mediamtxConfig;
- @Resource
- private RedisCache redisCache;
/**
* 查询通道管理
@@ -143,102 +136,5 @@
return UserConstants.NOT_UNIQUE;
}
return UserConstants.UNIQUE;
- }
-
-
- @Scheduled(fixedDelay = 5000)
- public void syncVtdu() {
- log.debug("定时同步Vtdu任务");
- List<ArdChannel> channels = this.selectArdChannelList(new ArdChannel());
- if (channels == null || channels.isEmpty()) {
- // log.warn("没有获取到通道数据,退出同步");
- return;
- }
- List<ArdVtdu> ardVtduList = ardVtduService.selectArdVtduList(new ArdVtdu());
-
- Set<String> validNames = channels.stream()
- .map(channel -> channel.getDeviceId() + "_" + channel.getChanNo())
- .collect(Collectors.toSet());
- // 2. 合并操作:增、删、改
- channels.forEach(channel -> {
- String name = channel.getDeviceId() + "_" + channel.getChanNo(); // 生成媒体表的 name
- String cameraId = channel.getDeviceId(); // cameraId 就是通道的 deviceId
- String rtspSourceUrl = getRtspSource(channel);
- String rtspUrl =
- "rtsp://" + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getRtspPort() + "/" + name; //
- // 拼接 webrtcUrl
- String rtmpUrl =
- "rtmp://" + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getRtmpPort() + "/" + name; // 拼接
- // webrtcUrl
- String protocol = mediamtxConfig.isHttps() ? "https://" : "http://";
- String webrtcUrl =
- protocol + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getWebrtcPort() + "/" + name;
- // 拼接 webrtcUrl
-
- // 先检查媒体表是否已有此记录
- Optional<ArdVtdu> existingMedia = ardVtduList.stream()
- .filter(media -> media.getName().equals(name)) // 查找是否已有对应的媒体记录
- .findFirst();
-
- if (existingMedia.isPresent()) {
- // 如果已有,更新现有记录
- ArdVtdu vtdu = existingMedia.get();
- // 更新前后内容相同则不进行更新
- if (!vtdu.getRtspSource().equals(rtspSourceUrl) ||
- !vtdu.getWebrtcUrl().equals(webrtcUrl) ||
- !vtdu.getRtspUrl().equals(rtspUrl) ||
- !vtdu.getRtmpUrl().equals(rtmpUrl)) {
- vtdu.setRtspUrl(rtspUrl);
- vtdu.setRtmpUrl(rtmpUrl);
- vtdu.setWebrtcUrl(webrtcUrl);
- ardVtduService.updateArdVtdu(vtdu);
- log.info("更新媒体记录:{}", vtdu.getName());
- }
- } else {
- // 如果没有,插入新的记录
- ArdVtdu vtdu = new ArdVtdu();
- vtdu.setName(name);
- vtdu.setRtspSource(rtspSourceUrl);
- vtdu.setCameraId(cameraId);
- vtdu.setRtspUrl(rtspUrl);
- vtdu.setRtmpUrl(rtmpUrl);
- vtdu.setWebrtcUrl(webrtcUrl);
- ardVtduService.insertArdVtdu(vtdu); // 保存到媒体表
- log.info("插入新媒体记录:{}", vtdu.getName());
- }
- });
- // 3. 删除操作:删除媒体表中多余的记录
- List<String> invalidNames = ardVtduList.stream()
- .filter(vtdu -> !validNames.contains(vtdu.getName()))
- .filter(vtdu -> !"publisher".equalsIgnoreCase(vtdu.getRtspSource())) // 不是机场流
- .map(ArdVtdu::getName)
- .collect(Collectors.toList());
-
- if (!invalidNames.isEmpty()) {
- // 将 List<String> 转换为 String[]
- String[] namesArray = invalidNames.toArray(new String[0]);
- ardVtduService.deleteArdVtduByNames(namesArray); // 传递数组
- log.info("删除不再存在的媒体记录:{}", Arrays.toString(namesArray));
- }
-
- }
-
- // 根据通道信息获取rtsp源地址
- private String getRtspSource(ArdChannel channel) {
- String rtspSource = "";
- ArdCamera ardCamera = redisCache.getCacheObject(CacheConstants.CAMERA_LIST_KEY + channel.getDeviceId());
- if (ardCamera != null) {
- switch (ardCamera.getFactory()) {
- case "1":
- rtspSource =
- "rtsp://" + ardCamera.getUsername() + ":" + ardCamera.getPassword() + "@" + ardCamera.getIp() + ":" + ardCamera.getRtspPort() + "/h264/ch" + channel.getChanNo() + "/main/av_stream";
- break;
- case "2":
- rtspSource =
- "rtsp://" + ardCamera.getUsername() + ":" + ardCamera.getPassword() + "@" + ardCamera.getIp() + ":" + ardCamera.getRtspPort() + "/cam/realmonitor?channel=" + channel.getChanNo() + "&subtype=0";
- break;
- }
- }
- return rtspSource;
}
}
\ No newline at end of file
diff --git a/ard-quartz/pom.xml b/ard-quartz/pom.xml
index 6c743db..fbff55e 100644
--- a/ard-quartz/pom.xml
+++ b/ard-quartz/pom.xml
@@ -38,6 +38,10 @@
<groupId>com.ard</groupId>
<artifactId>ard-work</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.ard</groupId>
+ <artifactId>ard-device</artifactId>
+ </dependency>
</dependencies>
diff --git a/ard-quartz/src/main/java/com/ard/quartz/task/DeviceTask.java b/ard-quartz/src/main/java/com/ard/quartz/task/DeviceTask.java
new file mode 100644
index 0000000..b51d24a
--- /dev/null
+++ b/ard-quartz/src/main/java/com/ard/quartz/task/DeviceTask.java
@@ -0,0 +1,258 @@
+package com.ard.quartz.task;
+
+import com.ard.common.constant.CacheConstants;
+import com.ard.common.core.redis.RedisCache;
+import com.ard.common.utils.DateUtils;
+import com.ard.common.utils.file.MimeTypeUtils;
+import com.ard.device.camera.domain.ArdCamera;
+import com.ard.device.camera.service.IArdCameraService;
+import com.ard.device.channel.domain.ArdChannel;
+import com.ard.device.channel.service.IArdChannelService;
+import com.ard.device.component.sdk.service.CameraSDKService;
+import com.ard.oss.service.impl.OssServiceContext;
+import com.ard.vtdu.config.MediamtxConfig;
+import com.ard.vtdu.domain.ArdVtdu;
+import com.ard.vtdu.service.IArdMediaService;
+import com.ard.vtdu.service.IArdVtduService;
+import com.ard.work.domain.entity.ArdMediaPic;
+import com.ard.work.domain.entity.ArdMediaVideo;
+import com.ard.work.mapper.ArdMediaPicMapper;
+import com.ard.work.mapper.ArdMediaVideoMapper;
+import com.ard.work.util.osSelect;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+/**
+ * 定时同步设备任务
+ *
+ * @author ard
+ */
+@Component("DeviceTask")
+@Slf4j
+public class DeviceTask {
+ @Resource
+ private RedisCache redisCache;
+ @Resource
+ private IArdChannelService ardChannelService;
+ @Resource
+ private IArdVtduService ardVtduService;
+ @Resource
+ private MediamtxConfig mediamtxConfig;
+ @Resource
+ private CameraSDKService cameraSDKService;
+ @Resource
+ private IArdCameraService ardCameraService;
+ @Resource
+ private IArdMediaService ardMediaService;
+
+
+
+ /**
+ * 定时执行登录失败相机重新登录
+ */
+ public void failedReconnect() {
+ try {
+ log.debug("定时执行失败相机重连任务");
+ // 从数据库取全部设备
+ ArdCamera ardCamera = new ArdCamera();
+ ardCamera.setUseDataScope(false);
+ List<ArdCamera> cameraList = ardCameraService.selectArdCameraList(ardCamera);
+ cameraList.stream()
+ // 只处理登录失败或离线的设备
+ .filter(camera -> camera.getLoginId() == null || camera.getLoginId() < 0 || "0".equals(camera.getState()))
+ .forEach(camera -> {
+ log.info("尝试重新登录设备 {}:{}", camera.getIp(), camera.getPort());
+ cameraSDKService.login(camera);
+ });
+
+ } catch (Exception ex) {
+ log.error("定时执行失败相机重连任务异常:{}", ex.getMessage());
+ }
+ }
+
+ /**
+ * 同步相机在线状态
+ */
+ public void syncCameraState() {
+ try {
+ log.debug("定时执行同步相机在线状态任务");
+ //调用查询方法
+ ArdCamera ardCamera = new ArdCamera();
+ ardCamera.setUseDataScope(false);
+ List<ArdCamera> ardCameraList = ardCameraService.selectArdCameraList(ardCamera);
+ ardCameraList
+ .forEach(camera -> {
+ boolean onLineNew = cameraSDKService.isOnLine(camera);
+ boolean onLineOld = "1".equals(camera.getState());
+ if (onLineNew != onLineOld) {
+ if (onLineNew) {
+ camera.setState("1");
+ } else {
+ camera.setState("0");
+ }
+ camera.setUpdateTime(DateUtils.getNowDate());
+ ardCameraService.updateArdCamera(camera);
+ }
+ });
+ } catch (Exception ex) {
+ log.error("同步相机在线状态异常:{}", ex.getMessage());
+ }
+ }
+
+ /**
+ * 同步相机通道
+ */
+ public void syncChannel() {
+ try {
+ log.debug("定时执行同步通道任务");
+ ArdCamera camera = new ArdCamera();
+ camera.setUseDataScope(false);
+ List<ArdCamera> ardCameraList = ardCameraService.selectArdCameraList(camera);
+ if (!ardCameraList.isEmpty()) {
+ ardCameraList
+ .forEach(ardCamera -> {
+ //SDK获取实际通道
+ List<ArdChannel> ardChannelListSDK = cameraSDKService.getChannels(ardCamera);
+ //数据库获取通道
+ ArdChannel ardChannel = new ArdChannel();
+ ardChannel.setUseDataScope(false);
+ ardChannel.setDeviceId(ardCamera.getId());
+ List<ArdChannel> ardChannelListDb = ardChannelService.selectArdChannelList(ardChannel);
+ //同步通道
+ ardCameraService.asyncChannel(ardChannelListDb, ardChannelListSDK);
+ });
+ }
+ } catch (Exception ex) {
+ log.error("定时执行同步通道任务异常:{}", ex.getMessage());
+ }
+ }
+
+ /**
+ * 同步通道流媒体
+ */
+ public void syncVtdu() {
+ log.debug("定时同步Vtdu任务");
+ ArdChannel ardChannel = new ArdChannel();
+ ardChannel.setUseDataScope(false);
+ List<ArdChannel> channels = ardChannelService.selectArdChannelList(ardChannel);
+ if (channels == null || channels.isEmpty()) {
+ // log.warn("没有获取到通道数据,退出同步");
+ return;
+ }
+ List<ArdVtdu> ardVtduList = ardVtduService.selectArdVtduList(new ArdVtdu());
+
+ Set<String> validNames = channels.stream()
+ .map(channel -> channel.getDeviceId() + "_" + channel.getChanNo())
+ .collect(Collectors.toSet());
+ // 2. 合并操作:增、删、改
+ channels.forEach(channel -> {
+ String name = channel.getDeviceId() + "_" + channel.getChanNo(); // 生成媒体表的 name
+ String cameraId = channel.getDeviceId(); // cameraId 就是通道的 deviceId
+ String rtspSourceUrl = getRtspSource(channel);
+ String rtspUrl =
+ "rtsp://" + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getRtspPort() + "/" + name; //
+ // 拼接 webrtcUrl
+ String rtmpUrl =
+ "rtmp://" + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getRtmpPort() + "/" + name; // 拼接
+ // webrtcUrl
+ String protocol = mediamtxConfig.isHttps() ? "https://" : "http://";
+ String webrtcUrl =
+ protocol + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getWebrtcPort() + "/" + name;
+ // 拼接 webrtcUrl
+
+ // 先检查媒体表是否已有此记录
+ Optional<ArdVtdu> existingMedia = ardVtduList.stream()
+ .filter(media -> media.getName().equals(name)) // 查找是否已有对应的媒体记录
+ .findFirst();
+
+ if (existingMedia.isPresent()) {
+ // 如果已有,更新现有记录
+ ArdVtdu vtdu = existingMedia.get();
+ // 更新前后内容相同则不进行更新
+ if (!vtdu.getRtspSource().equals(rtspSourceUrl) ||
+ !vtdu.getWebrtcUrl().equals(webrtcUrl) ||
+ !vtdu.getRtspUrl().equals(rtspUrl) ||
+ !vtdu.getRtmpUrl().equals(rtmpUrl)) {
+ vtdu.setRtspUrl(rtspUrl);
+ vtdu.setRtmpUrl(rtmpUrl);
+ vtdu.setWebrtcUrl(webrtcUrl);
+ ardVtduService.updateArdVtdu(vtdu);
+ log.info("更新媒体记录:{}", vtdu.getName());
+ }
+ } else {
+ // 如果没有,插入新的记录
+ ArdVtdu vtdu = new ArdVtdu();
+ vtdu.setName(name);
+ vtdu.setRtspSource(rtspSourceUrl);
+ vtdu.setCameraId(cameraId);
+ vtdu.setRtspUrl(rtspUrl);
+ vtdu.setRtmpUrl(rtmpUrl);
+ vtdu.setWebrtcUrl(webrtcUrl);
+ ardVtduService.insertArdVtdu(vtdu); // 保存到媒体表
+ log.info("插入新媒体记录:{}", vtdu.getName());
+ }
+ });
+ // 3. 删除操作:删除媒体表中多余的记录
+ List<String> invalidNames = ardVtduList.stream()
+ .filter(vtdu -> !validNames.contains(vtdu.getName()))
+ .filter(vtdu -> !"publisher".equalsIgnoreCase(vtdu.getRtspSource())) // 不是机场流
+ .map(ArdVtdu::getName)
+ .collect(Collectors.toList());
+
+ if (!invalidNames.isEmpty()) {
+ // 将 List<String> 转换为 String[]
+ String[] namesArray = invalidNames.toArray(new String[0]);
+ ardVtduService.deleteArdVtduByNames(namesArray); // 传递数组
+ log.info("删除不再存在的媒体记录:{}", Arrays.toString(namesArray));
+ }
+
+ }
+
+ // 根据通道信息获取rtsp源地址
+ private String getRtspSource(ArdChannel channel) {
+ String rtspSource = "";
+ ArdCamera ardCamera = redisCache.getCacheObject(CacheConstants.CAMERA_LIST_KEY + channel.getDeviceId());
+ if (ardCamera != null) {
+ switch (ardCamera.getFactory()) {
+ case "1":
+ rtspSource =
+ "rtsp://" + ardCamera.getUsername() + ":" + ardCamera.getPassword() + "@" + ardCamera.getIp() + ":" + ardCamera.getRtspPort() + "/h264/ch" + channel.getChanNo() + "/main/av_stream";
+ break;
+ case "2":
+ rtspSource =
+ "rtsp://" + ardCamera.getUsername() + ":" + ardCamera.getPassword() + "@" + ardCamera.getIp() + ":" + ardCamera.getRtspPort() + "/cam/realmonitor?channel=" + channel.getChanNo() + "&subtype=0";
+ break;
+ }
+ }
+ return rtspSource;
+ }
+
+ /**
+ * 同步mediamtx
+ */
+ public void syncMediamtx() {
+ log.debug("定时同步MediaMTX任务");
+ List<String> nameList = ardMediaService.getNameList();
+ List<ArdVtdu> ardVtduList = ardVtduService.selectArdVtduList(new ArdVtdu());
+ ardVtduService.asyncMediaMTX(ardVtduList, nameList);
+ }
+}
diff --git a/ard-vtdu/src/main/java/com/ard/vtdu/service/impl/ArdVtduServiceImpl.java b/ard-vtdu/src/main/java/com/ard/vtdu/service/impl/ArdVtduServiceImpl.java
index 33a62f3..806b065 100644
--- a/ard-vtdu/src/main/java/com/ard/vtdu/service/impl/ArdVtduServiceImpl.java
+++ b/ard-vtdu/src/main/java/com/ard/vtdu/service/impl/ArdVtduServiceImpl.java
@@ -38,14 +38,6 @@
mediaService.clearAll();
}
- @Scheduled(fixedDelay = 5000)
- public void syncMediamtx() {
- log.debug("定时同步MediaMTX任务");
- List<String> nameList = mediaService.getNameList();
- List<ArdVtdu> ardVtduList = selectArdVtduList(new ArdVtdu());
- asyncMediaMTX(ardVtduList, nameList);
- }
-
/**
* 查询流媒体管理
*
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java b/ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java
index 560cc25..7064f3f 100644
--- a/ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java
+++ b/ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java
@@ -362,32 +362,5 @@
}
- /**
- * 更新 localBoxInfo 的字段,如果有变化返回 true,否则返回 false
- */
- private boolean updateChangedAttributes(ArdDroneInfo localBoxInfo, BoxInfoReceiver remoteArdDrone) {
- boolean changed = false;
-
- if (!Objects.equals(localBoxInfo.getBoxName(), remoteArdDrone.getBoxName())) {
- localBoxInfo.setBoxName(remoteArdDrone.getBoxName());
- changed = true;
- }
- if (!Objects.equals(localBoxInfo.getBoxVersion(), remoteArdDrone.getBoxVersion())) {
- localBoxInfo.setBoxVersion(remoteArdDrone.getBoxVersion());
- changed = true;
- }
- if (!Objects.equals(localBoxInfo.getLastTaskId(), remoteArdDrone.getLastTaskId())) {
- localBoxInfo.setLastTaskId(remoteArdDrone.getLastTaskId());
- changed = true;
- }
- if (!Objects.equals(localBoxInfo.getOnLine(), remoteArdDrone.getOnLine())) {
- localBoxInfo.setOnLine(remoteArdDrone.getOnLine());
- changed = true;
- }
-
- // TODO: 更新其他属性时,如果变化也设置 changed = true
-
- return changed;
- }
}
--
Gitblit v1.9.3