diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5a0df17..759ccbf 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -24,7 +24,23 @@ "mcp__context7__resolve-library-id", "mcp__context7__query-docs", "Read(//Users/wangbiao/.config/**)", - "mcp__mysql__mysql_query" + "mcp__mysql__mysql_query", + "Bash(redis-cli ping *)", + "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8008/cmd/service)", + "Bash(curl *)", + "Bash(redis-cli keys *)", + "Bash(redis-cli KEYS \"*\")", + "Bash(redis-cli -n 0 KEYS \"*\")", + "Bash(redis-cli -n 1 KEYS \"*\")", + "Bash(redis-cli -n 0 --scan --pattern \"*NzQyNzQ*\")", + "Bash(redis-cli -n 1 --scan --pattern \"*\")", + "Bash(redis-cli -n 2 --scan --pattern \"*\")", + "Bash(redis-cli GET \"NzQyNzQ3MWYtYjAzYi00MWJkLWIyM2MtZTEzM2QzNTI1ZTBi_validateCode\")", + "Bash(xxd)", + "Bash(mcp__mysql__mysql_query {\"sql\": \"SELECT * FROM file_rel WHERE obj_id = \\\\\"822026051757190322\\\\\"\"} *)", + "Bash(mcp__mysql__mysql_query {\"sql\": \"SELECT * FROM repair_pool WHERE repair_id = \\\\\"822026051757190322\\\\\"\"} *)", + "Bash(uuidgen)", + "Bash(./gradlew compileDebugKotlin)" ] } } diff --git a/java110-utils/src/main/java/com/java110/utils/util/Base64Convert.java b/java110-utils/src/main/java/com/java110/utils/util/Base64Convert.java index 23edb02..d5ce770 100755 --- a/java110-utils/src/main/java/com/java110/utils/util/Base64Convert.java +++ b/java110-utils/src/main/java/com/java110/utils/util/Base64Convert.java @@ -20,20 +20,19 @@ public class Base64Convert { * @throws IOException */ public static String ioToBase64(InputStream in) throws IOException { - String strBase64 = null; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try { - // in.available()返回文件的字节长度 - byte[] bytes = new byte[in.available()]; - // 将文件中的内容读入到数组中 - in.read(bytes); - strBase64 = base64.encodeToString(bytes); //将字节流数组转换为字符串 + byte[] temp = new byte[8192]; + int bytesRead; + while ((bytesRead = in.read(temp)) != -1) { + buffer.write(temp, 0, bytesRead); + } } finally { if (in != null) { in.close(); } } - - return strBase64; + return base64.encodeToString(buffer.toByteArray()); } /** diff --git a/service-api/src/main/java/com/java110/api/smo/file/impl/AddFileSMOImpl.java b/service-api/src/main/java/com/java110/api/smo/file/impl/AddFileSMOImpl.java index 7dc3853..5fe8407 100644 --- a/service-api/src/main/java/com/java110/api/smo/file/impl/AddFileSMOImpl.java +++ b/service-api/src/main/java/com/java110/api/smo/file/impl/AddFileSMOImpl.java @@ -19,6 +19,8 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; +import com.java110.vo.ResultVo; + import java.io.IOException; import java.io.InputStream; @@ -97,19 +99,12 @@ public class AddFileSMOImpl extends DefaultAbstractComponentSMO implements IAddF String fileName = fileInnerServiceSMOImpl.saveFile(fileDto); - JSONObject outParam = new JSONObject(); - outParam.put("fileId", fileName); + JSONObject data = new JSONObject(); + data.put("fileId", fileName); String imgUrl = MappingCache.getValue(MappingConstant.FILE_DOMAIN,"IMG_PATH"); - outParam.put("url", imgUrl + fileName); - - ResponseEntity responseEntity = new ResponseEntity(outParam.toJSONString(), HttpStatus.OK); - -// String apiUrl = "file.saveFile" ; + data.put("url", imgUrl + fileName); -// ResponseEntity responseEntity = this.callCenterService(restTemplate, pd, paramIn.toJSONString(), -// apiUrl, -// HttpMethod.POST); - return responseEntity; + return ResultVo.createResponseEntity(data); } diff --git a/service-common/src/main/java/com/java110/common/bmo/mall/impl/SaveOwnerRepairImpl.java b/service-common/src/main/java/com/java110/common/bmo/mall/impl/SaveOwnerRepairImpl.java index a48b8d7..5d4d0f3 100644 --- a/service-common/src/main/java/com/java110/common/bmo/mall/impl/SaveOwnerRepairImpl.java +++ b/service-common/src/main/java/com/java110/common/bmo/mall/impl/SaveOwnerRepairImpl.java @@ -208,6 +208,10 @@ public class SaveOwnerRepairImpl implements IMallCommonApiBmo { // 遍历所有图片 for (int _photoIndex = 0; _photoIndex < photos.size(); _photoIndex++) { String _photo = photos.getString(_photoIndex); + // 跳过无效的图片值 + if (_photo == null || _photo.isEmpty() || "null".equals(_photo)) { + continue; + } // 如果图片内容过长(base64编码),先保存到文件系统 if (_photo.length() > 512) { diff --git a/service-community/src/main/java/com/java110/community/cmd/ownerRepair/RepairFinishCmd.java b/service-community/src/main/java/com/java110/community/cmd/ownerRepair/RepairFinishCmd.java index 220eff9..d394a50 100644 --- a/service-community/src/main/java/com/java110/community/cmd/ownerRepair/RepairFinishCmd.java +++ b/service-community/src/main/java/com/java110/community/cmd/ownerRepair/RepairFinishCmd.java @@ -565,6 +565,9 @@ public class RepairFinishCmd extends Cmd { JSONArray beforeRepairPhotos = reqJson.getJSONArray("beforeRepairPhotos"); for (int _photoIndex = 0; _photoIndex < beforeRepairPhotos.size(); _photoIndex++) { String photo = beforeRepairPhotos.getJSONObject(_photoIndex).getString("photo"); + if (photo == null || photo.isEmpty() || "null".equals(photo)) { + continue; + } if (photo.length() > 512) { FileDto fileDto = new FileDto(); fileDto.setFileId(GenerateCodeFactory.getGeneratorId(GenerateCodeFactory.CODE_PREFIX_file_id)); @@ -593,6 +596,9 @@ public class RepairFinishCmd extends Cmd { JSONArray afterRepairPhotos = reqJson.getJSONArray("afterRepairPhotos"); for (int _photoIndex = 0; _photoIndex < afterRepairPhotos.size(); _photoIndex++) { String photo = afterRepairPhotos.getJSONObject(_photoIndex).getString("photo"); + if (photo == null || photo.isEmpty() || "null".equals(photo)) { + continue; + } if (photo.length() > 512) { FileDto fileDto = new FileDto(); fileDto.setFileId(GenerateCodeFactory.getGeneratorId(GenerateCodeFactory.CODE_PREFIX_file_id)); diff --git a/service-community/src/main/java/com/java110/community/cmd/ownerRepair/SaveOwnerRepairCmd.java b/service-community/src/main/java/com/java110/community/cmd/ownerRepair/SaveOwnerRepairCmd.java index 30672c1..1839cbc 100644 --- a/service-community/src/main/java/com/java110/community/cmd/ownerRepair/SaveOwnerRepairCmd.java +++ b/service-community/src/main/java/com/java110/community/cmd/ownerRepair/SaveOwnerRepairCmd.java @@ -283,7 +283,11 @@ public class SaveOwnerRepairCmd extends Cmd { // 遍历所有图片 for (int _photoIndex = 0; _photoIndex < photos.size(); _photoIndex++) { String _photo = photos.getString(_photoIndex); - + // 跳过无效的图片值 + if (_photo == null || _photo.isEmpty() || "null".equals(_photo)) { + continue; + } + // 如果图片内容过长(base64编码),先保存到文件系统 if (_photo.length() > 512) { FileDto fileDto = new FileDto(); diff --git a/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceRecordsCmd.java b/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceRecordsCmd.java index 1cf2998..8d7a2de 100644 --- a/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceRecordsCmd.java +++ b/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceRecordsCmd.java @@ -48,9 +48,7 @@ public class QueryAttendanceRecordsCmd extends Cmd { private Map buildQueryParams(JSONObject reqJson) { Map params = new HashMap<>(); - // 跳过网关自动注入的 userId(等于 loginUserId 时说明是网关注入的,不是用户筛选的) - if (reqJson.containsKey("userId") - && !reqJson.getString("userId").equals(reqJson.getString("loginUserId"))) { + if (reqJson.containsKey("userId")) { params.put("userId", reqJson.getString("userId")); } if (reqJson.containsKey("workType")) params.put("workType", reqJson.getString("workType")); diff --git a/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceReminderCmd.java b/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceReminderCmd.java new file mode 100644 index 0000000..0bc90ae --- /dev/null +++ b/service-user/src/main/java/com/java110/user/cmd/property/QueryAttendanceReminderCmd.java @@ -0,0 +1,215 @@ +package com.java110.user.cmd.property; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.java110.core.annotation.Java110Cmd; +import com.java110.core.context.ICmdDataFlowContext; +import com.java110.core.event.cmd.Cmd; +import com.java110.core.event.cmd.CmdEvent; +import com.java110.user.dao.property.IAttendanceReminderV1ServiceDao; +import com.java110.utils.exception.CmdException; +import com.java110.utils.util.Assert; +import com.java110.vo.ResultVo; +import org.springframework.beans.factory.annotation.Autowired; + +import java.text.SimpleDateFormat; +import java.util.*; + +@Java110Cmd(serviceCode = "property.queryAttendanceReminder") +public class QueryAttendanceReminderCmd extends Cmd { + + @Autowired + private IAttendanceReminderV1ServiceDao attendanceReminderV1ServiceDao; + + @Override + public void validate(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) { + Assert.hasKeyAndValue(reqJson, "userId", "未包含用户ID"); + } + + @Override + public void doCmd(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) { + String userId = reqJson.getString("userId"); + String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + Calendar cal = Calendar.getInstance(); + int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); + // Calendar.SUNDAY=1, MONDAY=2, ..., but schedule_classes_day uses week_flag differently + // week_flag: 1=周一, 2=周二, ..., 7=周日 + int weekFlag = dayOfWeek == Calendar.SUNDAY ? 7 : dayOfWeek - 1; + + JSONArray overdueShifts = new JSONArray(); + boolean hasReminder = false; + + // 1. Query staff's schedule assignments + Map staffParams = new HashMap<>(); + staffParams.put("staffId", userId); + List staffSchedules = attendanceReminderV1ServiceDao.queryStaffSchedules(staffParams); + + if (staffSchedules == null || staffSchedules.isEmpty()) { + // No schedule assigned → default to "always working", check if clocked in today + checkDefaultReminder(userId, today, overdueShifts); + if (!overdueShifts.isEmpty()) hasReminder = true; + JSONObject result = new JSONObject(); + result.put("hasReminder", hasReminder); + result.put("overdueShifts", overdueShifts); + context.setResponseEntity(ResultVo.createResponseEntity(result)); + return; + } + + // 2. For each schedule, determine today's work times + for (Map staffSchedule : staffSchedules) { + String scheduleId = (String) staffSchedule.get("scheduleId"); + + // Query schedule definition + Map schedParams = new HashMap<>(); + schedParams.put("scheduleId", scheduleId); + List schedules = attendanceReminderV1ServiceDao.queryScheduleById(schedParams); + if (schedules == null || schedules.isEmpty()) continue; + Map schedule = schedules.get(0); + + String scheduleType = (String) schedule.get("scheduleType"); + if (!"1001".equals(schedule.get("state"))) continue; // not started + + List dayEntries = null; + + if ("1001".equals(scheduleType)) { + // Day-based schedule + int scheduleCycle = Integer.parseInt(safeString(schedule.get("scheduleCycle"))); + String computeTimeStr = safeString(schedule.get("computeTime")); + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date computeDate = sdf.parse(computeTimeStr); + long daysBetween = (cal.getTimeInMillis() - computeDate.getTime()) / (24 * 3600 * 1000); + int dayInCycle = (int) ((daysBetween % scheduleCycle) + 1); + + Map dayParams = new HashMap<>(); + dayParams.put("scheduleId", scheduleId); + dayParams.put("day", String.valueOf(dayInCycle)); + dayEntries = attendanceReminderV1ServiceDao.queryScheduleDay(dayParams); + } catch (Exception e) { + continue; + } + } else if ("2002".equals(scheduleType)) { + // Week-based schedule + Map dayParams = new HashMap<>(); + dayParams.put("scheduleId", scheduleId); + dayParams.put("day", String.valueOf(weekFlag)); + dayParams.put("weekFlag", String.valueOf(weekFlag)); + dayEntries = attendanceReminderV1ServiceDao.queryScheduleDay(dayParams); + } else if ("3003".equals(scheduleType)) { + // Month-based schedule + Map dayParams = new HashMap<>(); + dayParams.put("scheduleId", scheduleId); + dayParams.put("day", String.valueOf(dayOfMonth)); + dayEntries = attendanceReminderV1ServiceDao.queryScheduleDay(dayParams); + } + + if (dayEntries == null || dayEntries.isEmpty()) continue; + + for (Map dayEntry : dayEntries) { + String workday = (String) dayEntry.get("workday"); + if ("2002".equals(workday)) continue; // rest day + + String dayId = (String) dayEntry.get("dayId"); + Map timeParams = new HashMap<>(); + timeParams.put("dayId", dayId); + List times = attendanceReminderV1ServiceDao.queryScheduleTimes(timeParams); + + if (times == null || times.isEmpty()) continue; + + // 3. Check if clocked in today + Map punchParams = new HashMap<>(); + punchParams.put("userId", userId); + punchParams.put("punchType", "ON"); + Map todayPunch = attendanceReminderV1ServiceDao.queryTodayPunch(punchParams); + + for (Map time : times) { + String startTime = (String) time.get("startTime"); + String endTime = (String) time.get("endTime"); + + String fullStartTime = today + " " + startTime + ":00"; + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date startDt = sdf.parse(fullStartTime); + Date now = new Date(); + + long diffMinutes = (now.getTime() - startDt.getTime()) / (60 * 1000); + // Within 5 minutes after start time, or after + if (diffMinutes >= 0 && diffMinutes <= 120) { + boolean clockedIn = todayPunch != null; + if (!clockedIn) { + hasReminder = true; + JSONObject shift = new JSONObject(); + shift.put("startTime", startTime); + shift.put("endTime", endTime); + shift.put("fullStartTime", fullStartTime); + shift.put("scheduleName", staffSchedule.getOrDefault("scheduleName", "")); + shift.put("overdueMinutes", diffMinutes); + overdueShifts.add(shift); + } + } + } catch (Exception ignored) { + } + } + } + } + + // Fallback: if no overdue shifts found from schedule, but staff has schedule and no clockin, + // use default time window to check + if (!hasReminder && overdueShifts.isEmpty()) { + Map punchParams = new HashMap<>(); + punchParams.put("userId", userId); + punchParams.put("punchType", "ON"); + Map todayPunch = attendanceReminderV1ServiceDao.queryTodayPunch(punchParams); + if (todayPunch == null) { + checkDefaultReminder(userId, today, overdueShifts); + if (!overdueShifts.isEmpty()) hasReminder = true; + } + } + + JSONObject result = new JSONObject(); + result.put("hasReminder", hasReminder); + result.put("overdueShifts", overdueShifts); + context.setResponseEntity(ResultVo.createResponseEntity(result)); + } + + /** + * Default reminder check: when no schedule, assume staff should be working. + * Checks if current time is past 8:00 AM and no clock-in today. + */ + private void checkDefaultReminder(String userId, String today, JSONArray overdueShifts) { + try { + // Default work window: 08:00 - 18:00 + String defaultStart = today + " 08:00:00"; + String defaultEnd = today + " 18:00:00"; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date startDt = sdf.parse(defaultStart); + Date endDt = sdf.parse(defaultEnd); + Date now = new Date(); + + // Only remind between 8:00 and 18:00, and at least 5 min past start + if (now.after(startDt) && now.before(endDt)) { + long diffMinutes = (now.getTime() - startDt.getTime()) / (60 * 1000); + if (diffMinutes >= 0 && diffMinutes <= 600) { // within 10 hours + JSONObject shift = new JSONObject(); + shift.put("startTime", "08:00"); + shift.put("endTime", "18:00"); + shift.put("fullStartTime", defaultStart); + shift.put("scheduleName", "默认班次"); + shift.put("overdueMinutes", diffMinutes); + overdueShifts.add(shift); + } + } + } catch (Exception ignored) { + } + } + + private String safeString(Object obj) { + if (obj == null) return ""; + if (obj instanceof String) return (String) obj; + if (obj instanceof java.sql.Timestamp) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((java.sql.Timestamp) obj); + } + return obj.toString(); + } +} diff --git a/service-user/src/main/java/com/java110/user/dao/impl/AttendanceReminderV1ServiceDaoImpl.java b/service-user/src/main/java/com/java110/user/dao/impl/AttendanceReminderV1ServiceDaoImpl.java new file mode 100644 index 0000000..91e9234 --- /dev/null +++ b/service-user/src/main/java/com/java110/user/dao/impl/AttendanceReminderV1ServiceDaoImpl.java @@ -0,0 +1,45 @@ +package com.java110.user.dao.impl; + +import com.java110.core.base.dao.BaseServiceDao; +import com.java110.user.dao.property.IAttendanceReminderV1ServiceDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service("attendanceReminderV1ServiceDaoImpl") +public class AttendanceReminderV1ServiceDaoImpl extends BaseServiceDao implements IAttendanceReminderV1ServiceDao { + + private static Logger logger = LoggerFactory.getLogger(AttendanceReminderV1ServiceDaoImpl.class); + + @Override + public List queryStaffSchedules(Map params) { + return sqlSessionTemplate.selectList("attendanceReminderV1ServiceDaoImpl.queryStaffSchedules", params); + } + + @Override + public List queryScheduleById(Map params) { + return sqlSessionTemplate.selectList("attendanceReminderV1ServiceDaoImpl.queryScheduleById", params); + } + + @Override + public List queryScheduleDay(Map params) { + return sqlSessionTemplate.selectList("attendanceReminderV1ServiceDaoImpl.queryScheduleDay", params); + } + + @Override + public List queryScheduleTimes(Map params) { + return sqlSessionTemplate.selectList("attendanceReminderV1ServiceDaoImpl.queryScheduleTimes", params); + } + + @Override + public Map queryTodayPunch(Map params) { + List list = sqlSessionTemplate.selectList("attendanceReminderV1ServiceDaoImpl.queryTodayPunch", params); + if (list != null && !list.isEmpty()) { + return list.get(0); + } + return null; + } +} diff --git a/service-user/src/main/java/com/java110/user/dao/property/IAttendanceReminderV1ServiceDao.java b/service-user/src/main/java/com/java110/user/dao/property/IAttendanceReminderV1ServiceDao.java new file mode 100644 index 0000000..5f4dbee --- /dev/null +++ b/service-user/src/main/java/com/java110/user/dao/property/IAttendanceReminderV1ServiceDao.java @@ -0,0 +1,12 @@ +package com.java110.user.dao.property; + +import java.util.List; +import java.util.Map; + +public interface IAttendanceReminderV1ServiceDao { + List queryStaffSchedules(Map params); + List queryScheduleById(Map params); + List queryScheduleDay(Map params); + List queryScheduleTimes(Map params); + Map queryTodayPunch(Map params); +} diff --git a/service-user/src/main/resources/mapper/property/AttendanceReminderMapper.xml b/service-user/src/main/resources/mapper/property/AttendanceReminderMapper.xml new file mode 100644 index 0000000..bf5927f --- /dev/null +++ b/service-user/src/main/resources/mapper/property/AttendanceReminderMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + diff --git a/springboot/src/main/resources/application-debug.yml b/springboot/src/main/resources/application-prod.yml index cafa6e9..dc27ecb 100644 --- a/springboot/src/main/resources/application-debug.yml +++ b/springboot/src/main/resources/application-prod.yml @@ -20,35 +20,37 @@ spring: application: name: boot-service redis: - database: 0 - host: 127.0.0.1 +# database: 0 +# host: 127.0.0.1 +# port: 6379 +# password: + database: 8 + host: xirong-redis.redis.rds.aliyuncs.com port: 6379 - password: hc - pool: - max-active: 300 - max-wait: 3000 - max-idle: 50 - min-idle: 20 - timeout: 0 + password: V3DRCkIiBH + activiti: database-schema-update: false datasource: - url: jdbc:mysql://192.168.31.250:3306/TT?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false - username: TT - password: hc12345678 + url: jdbc:mysql://rm-2zeo2635t3c592h2ywo.mysql.rds.aliyuncs.com:3306/estate?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 + username: estate + password: MySQL57@123 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 5 - max-active: 10 + max-active: 20 min-idle: 5 max-wait: 60000 - +eureka: + client: + enabled: false feign: client: config: default: connect-timeout: 10000 - read-timeout: 20000 \ No newline at end of file + read-timeout: 20000 + diff --git a/springboot/src/main/resources/application-zihao.yml b/springboot/src/main/resources/application-zihao.yml deleted file mode 100644 index 0c527fc..0000000 --- a/springboot/src/main/resources/application-zihao.yml +++ /dev/null @@ -1,53 +0,0 @@ -server: - port: 8008 - tomcat: - uri-encoding: UTF-8 - - - -spring: - servlet: - multipart: - maxFileSize: 50MB - maxRequestSize: 50MB - profiles: - active: share - http: - encoding: - charset: UTF-8 - enabled: true - force: true - application: - name: community-service - redis: - database: 0 - host: dev.redis.java110.com - port: 6379 - password: ${redispwd} - pool: - max-active: 300 - max-wait: 10000 - max-idle: 100 - min-idle: 0 - timeout: 0 - activiti: - database-schema-update: false - datasource: - url: jdbc:mysql://dev.db.java110.com:3306/TT?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 - username: TT - password: ${mysqlpwd} - type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - druid: - initial-size: 5 - max-active: 10 - min-idle: 5 - max-wait: 60000 - -feign: - client: - config: - default: - connect-timeout: 10000 - read-timeout: 20000 - diff --git a/springboot/src/main/resources/application.yml b/springboot/src/main/resources/application.yml index caf4dfc..9f96606 100644 --- a/springboot/src/main/resources/application.yml +++ b/springboot/src/main/resources/application.yml @@ -1,3 +1,3 @@ spring: profiles: - active: dev \ No newline at end of file + active: prod