diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QywxManualSyncController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QywxManualSyncController.java new file mode 100644 index 0000000..87315f8 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QywxManualSyncController.java @@ -0,0 +1,333 @@ +package com.agileboot.admin.controller.qywx; + +import com.agileboot.admin.customize.service.QywxScheduleJob; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; +import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; +import com.agileboot.domain.qywx.template.TemplateApplicationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 企业微信手动同步控制器 + * 用于手动触发企业微信定时任务,方便调试和即时同步 + * 该接口无需权限验证,路径为 /qywx/** 已在SecurityConfig中配置为permitAll + */ +@Tag(name = "企业微信手动同步API", description = "手动触发企业微信定时任务") +@RestController +@RequestMapping("/qywx/manual") +@RequiredArgsConstructor +@Slf4j +public class QywxManualSyncController { + + private final QywxScheduleJob qywxScheduleJob; + private final TemplateApplicationService templateApplicationService; + private final AuthCorpInfoApplicationService authCorpInfoApplicationService; + + /** + * 手动获取套件访问令牌(suite_access_token) + * 对应定时任务:@Scheduled(cron = "0 10 * * * *") + */ + @Operation(summary = "手动获取套件访问令牌", description = "获取企业微信应用凭证(suite_access_token)") + @PostMapping("/suite-access-token") + public ResponseDTO getSuiteAccessToken( + @Parameter(description = "应用ID,不传则执行所有应用") + @RequestParam(required = false) String appid) { + try { + if (appid != null && !appid.isEmpty()) { + // 执行单个应用 + qywxScheduleJob.getSuiteAccessToken(appid); + return ResponseDTO.ok("成功获取应用 " + appid + " 的suite_access_token"); + } else { + // 执行所有应用 + qywxScheduleJob.getSuiteAccessTokenTask(); + return ResponseDTO.ok("成功获取所有应用的suite_access_token"); + } + } catch (Exception e) { + log.error("手动获取suite_access_token失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "获取suite_access_token失败:" + e.getMessage())); + } + } + + /** + * 手动获取访问令牌(access_token) + * 对应定时任务:@Scheduled(cron = "0 20 * * * *") + */ + @Operation(summary = "手动获取访问令牌", description = "获取企业微信访问令牌(access_token)") + @PostMapping("/access-token") + public ResponseDTO getAccessToken( + @Parameter(description = "应用ID,不传则执行所有应用") + @RequestParam(required = false) String appid) { + try { + if (appid != null && !appid.isEmpty()) { + // 执行单个应用 + qywxScheduleJob.getAccessToken(appid); + return ResponseDTO.ok("成功获取应用 " + appid + " 的access_token"); + } else { + // 执行所有应用 + qywxScheduleJob.getAccessTokenTask(); + return ResponseDTO.ok("成功获取所有应用的access_token"); + } + } catch (Exception e) { + log.error("手动获取access_token失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "获取access_token失败:" + e.getMessage())); + } + } + + /** + * 手动同步部门信息 + * 对应定时任务:@Scheduled(cron = "0 30 * * * *") + */ + @Operation(summary = "手动同步部门信息", description = "从企业微信同步组织架构信息到本地数据库") + @PostMapping("/sync-department") + public ResponseDTO syncDepartment( + @Parameter(description = "应用ID,不传则执行所有应用") + @RequestParam(required = false) String appid, + @Parameter(description = "企业ID,不传则执行该应用下所有企业") + @RequestParam(required = false) String corpid) { + try { + if (appid != null && !appid.isEmpty() && corpid != null && !corpid.isEmpty()) { + // 执行指定应用和企业 + QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.selectByAppidAndCorpid(appid, corpid); + if (authCorpInfo != null) { + qywxScheduleJob.syncDepartmentInfo(appid, authCorpInfo); + return ResponseDTO.ok("成功同步应用 " + appid + " 企业 " + corpid + " 的部门信息"); + } else { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "未找到对应的授权企业信息")); + } + } else if (appid != null && !appid.isEmpty()) { + // 执行指定应用下的所有企业 + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (authCorpInfoList != null && !authCorpInfoList.isEmpty()) { + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + qywxScheduleJob.syncDepartmentInfo(appid, authCorpInfo); + } + return ResponseDTO.ok("成功同步应用 " + appid + " 下所有企业的部门信息"); + } else { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "该应用下未找到授权企业")); + } + } else { + // 执行所有应用 + qywxScheduleJob.syncDepartmentInfoTask(); + return ResponseDTO.ok("成功同步所有应用的部门信息"); + } + } catch (Exception e) { + log.error("手动同步部门信息失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "同步部门信息失败:" + e.getMessage())); + } + } + + /** + * 手动同步用户信息 + * 对应定时任务:@Scheduled(cron = "0 40 * * * *") + */ + @Operation(summary = "手动同步用户信息", description = "从企业微信同步用户信息到本地数据库") + @PostMapping("/sync-user") + public ResponseDTO syncUser( + @Parameter(description = "应用ID,不传则执行所有应用") + @RequestParam(required = false) String appid, + @Parameter(description = "企业ID,不传则执行该应用下所有企业") + @RequestParam(required = false) String corpid) { + try { + if (appid != null && !appid.isEmpty() && corpid != null && !corpid.isEmpty()) { + // 执行指定应用和企业 + QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.selectByAppidAndCorpid(appid, corpid); + if (authCorpInfo != null) { + qywxScheduleJob.syncUserInfo(appid, authCorpInfo); + return ResponseDTO.ok("成功同步应用 " + appid + " 企业 " + corpid + " 的用户信息"); + } else { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "未找到对应的授权企业信息")); + } + } else if (appid != null && !appid.isEmpty()) { + // 执行指定应用下的所有企业 + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (authCorpInfoList != null && !authCorpInfoList.isEmpty()) { + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + qywxScheduleJob.syncUserInfo(appid, authCorpInfo); + } + return ResponseDTO.ok("成功同步应用 " + appid + " 下所有企业的用户信息"); + } else { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "该应用下未找到授权企业")); + } + } else { + // 执行所有应用 + qywxScheduleJob.syncUserInfoTask(); + return ResponseDTO.ok("成功同步所有应用的用户信息"); + } + } catch (Exception e) { + log.error("手动同步用户信息失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "同步用户信息失败:" + e.getMessage())); + } + } + + /** + * 手动获取授权信息 + * 对应定时任务:@Scheduled(cron = "0 45 * * * *") + */ + @Operation(summary = "手动获取授权信息", description = "获取企业授权信息并更新到本地数据库") + @PostMapping("/auth-info") + public ResponseDTO getAuthInfo( + @Parameter(description = "应用ID,不传则执行所有应用") + @RequestParam(required = false) String appid) { + try { + if (appid != null && !appid.isEmpty()) { + // 执行单个应用 + qywxScheduleJob.getAuthInfo(appid); + return ResponseDTO.ok("成功获取应用 " + appid + " 的授权信息"); + } else { + // 执行所有应用 + qywxScheduleJob.getAuthInfoTask(); + return ResponseDTO.ok("成功获取所有应用的授权信息"); + } + } catch (Exception e) { + log.error("手动获取授权信息失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "获取授权信息失败:" + e.getMessage())); + } + } + + /** + * 手动同步用户绑定关系 + * 对应定时任务:@Scheduled(cron = "0 50 * * * *") + */ + @Operation(summary = "手动同步用户绑定关系", description = "同步企业微信用户与系统用户的绑定关系") + @PostMapping("/sync-user-bindings") + public ResponseDTO syncUserBindings() { + try { + qywxScheduleJob.syncUserBindings(); + return ResponseDTO.ok("成功同步用户绑定关系"); + } catch (Exception e) { + log.error("手动同步用户绑定关系失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "同步用户绑定关系失败:" + e.getMessage())); + } + } + + /** + * 执行完整同步流程 + * 按顺序执行:获取suite_access_token -> 获取access_token -> 同步部门 -> 同步用户 -> 获取授权信息 -> 同步绑定关系 + */ + @Operation(summary = "执行完整同步流程", description = "按顺序执行所有同步任务(推荐用于初始化或全量同步)") + @PostMapping("/sync-all") + public ResponseDTO syncAll( + @Parameter(description = "应用ID,不传则执行所有应用") + @RequestParam(required = false) String appid) { + try { + StringBuilder result = new StringBuilder(); + + // 1. 获取suite_access_token + if (appid != null && !appid.isEmpty()) { + qywxScheduleJob.getSuiteAccessToken(appid); + result.append("1. 已获取应用 ").append(appid).append(" 的suite_access_token\n"); + } else { + qywxScheduleJob.getSuiteAccessTokenTask(); + result.append("1. 已获取所有应用的suite_access_token\n"); + } + + // 等待一小段时间确保token生效 + Thread.sleep(1000); + + // 2. 获取access_token + if (appid != null && !appid.isEmpty()) { + qywxScheduleJob.getAccessToken(appid); + result.append("2. 已获取应用 ").append(appid).append(" 的access_token\n"); + } else { + qywxScheduleJob.getAccessTokenTask(); + result.append("2. 已获取所有应用的access_token\n"); + } + + Thread.sleep(1000); + + // 3. 同步部门信息 + if (appid != null && !appid.isEmpty()) { + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (authCorpInfoList != null && !authCorpInfoList.isEmpty()) { + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + qywxScheduleJob.syncDepartmentInfo(appid, authCorpInfo); + } + } + result.append("3. 已同步应用 ").append(appid).append(" 的部门信息\n"); + } else { + qywxScheduleJob.syncDepartmentInfoTask(); + result.append("3. 已同步所有应用的部门信息\n"); + } + + Thread.sleep(1000); + + // 4. 同步用户信息 + if (appid != null && !appid.isEmpty()) { + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (authCorpInfoList != null && !authCorpInfoList.isEmpty()) { + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + qywxScheduleJob.syncUserInfo(appid, authCorpInfo); + } + } + result.append("4. 已同步应用 ").append(appid).append(" 的用户信息\n"); + } else { + qywxScheduleJob.syncUserInfoTask(); + result.append("4. 已同步所有应用的用户信息\n"); + } + + Thread.sleep(1000); + + // 5. 获取授权信息 + if (appid != null && !appid.isEmpty()) { + qywxScheduleJob.getAuthInfo(appid); + result.append("5. 已获取应用 ").append(appid).append(" 的授权信息\n"); + } else { + qywxScheduleJob.getAuthInfoTask(); + result.append("5. 已获取所有应用的授权信息\n"); + } + + Thread.sleep(1000); + + // 6. 同步用户绑定关系 + qywxScheduleJob.syncUserBindings(); + result.append("6. 已同步用户绑定关系\n"); + + result.append("\n✅ 完整同步流程执行成功!"); + return ResponseDTO.ok(result.toString()); + } catch (Exception e) { + log.error("执行完整同步流程失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "执行完整同步流程失败:" + e.getMessage())); + } + } + + /** + * 获取所有可用的应用ID列表 + */ + @Operation(summary = "获取应用ID列表", description = "获取系统中所有已配置的企业微信应用ID") + @GetMapping("/appid-list") + public ResponseDTO> getAppidList() { + try { + List appidList = templateApplicationService.getTemplateAppidList(); + return ResponseDTO.ok(appidList); + } catch (Exception e) { + log.error("获取应用ID列表失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "获取应用ID列表失败:" + e.getMessage())); + } + } + + /** + * 获取指定应用下的所有企业列表 + */ + @Operation(summary = "获取企业列表", description = "获取指定应用下所有已授权的企业") + @GetMapping("/corp-list") + public ResponseDTO> getCorpList( + @Parameter(description = "应用ID", required = true) + @RequestParam String appid) { + try { + List corpList = authCorpInfoApplicationService.getByAppid(appid); + return ResponseDTO.ok(corpList); + } catch (Exception e) { + log.error("获取企业列表失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "获取企业列表失败:" + e.getMessage())); + } + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderMapper.java index 6e1b791..ce96585 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderMapper.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderMapper.java @@ -86,6 +86,7 @@ public interface ShopOrderMapper extends BaseMapper { "sog.goods_name as goodsName, " + "sog.price as goodsPrice, " + "sog.quantity as quantity, " + + "sog.cover_img as coverImg, " + "so.payment_method as paymentMethod, " + "so.name as orderName, " + "so.mobile as orderMobile, " + @@ -99,6 +100,7 @@ public interface ShopOrderMapper extends BaseMapper { "null as auditName, " + "null as auditRemark, " + "null as images, " + + "null as auditImages, " + "so.create_time as dynamic_time " + "FROM shop_order so " + "INNER JOIN shop_order_goods sog ON so.order_id = sog.order_id AND sog.deleted = 0 " + @@ -115,6 +117,7 @@ public interface ShopOrderMapper extends BaseMapper { "sog.goods_name as goodsName, " + "sog.price as goodsPrice, " + "sog.quantity as quantity, " + + "sog.cover_img as coverImg, " + "so.payment_method as paymentMethod, " + "so.name as orderName, " + "so.mobile as orderMobile, " + @@ -128,6 +131,7 @@ public interface ShopOrderMapper extends BaseMapper { "ra.audit_name as auditName, " + "ra.audit_remark as auditRemark, " + "ra.return_images as images, " + + "ra.audit_images as auditImages, " + "COALESCE(ra.approval_time, ra.create_time) as dynamic_time " + "FROM shop_order so " + "INNER JOIN shop_order_goods sog ON so.order_id = sog.order_id AND sog.deleted = 0 " + diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/BorrowReturnDynamicDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/BorrowReturnDynamicDTO.java index ab9f1c1..71422cb 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/BorrowReturnDynamicDTO.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/dto/BorrowReturnDynamicDTO.java @@ -36,6 +36,9 @@ public class BorrowReturnDynamicDTO { @ApiModelProperty("数量") private Integer quantity; + @ApiModelProperty("商品封面图片") + private String coverImg; + @ApiModelProperty("支付方式") private String paymentMethod; @@ -78,6 +81,9 @@ public class BorrowReturnDynamicDTO { @ApiModelProperty("归还图片(归还记录时有效)") private String images; + @ApiModelProperty("审批图片(归还记录时有效)") + private String auditImages; + public String getDynamicTypeStr() { if (dynamicType == null) return "-"; switch (dynamicType) { diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/query/SearchBorrowReturnDynamicQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/query/SearchBorrowReturnDynamicQuery.java index 08d18c4..59ac6b2 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/query/SearchBorrowReturnDynamicQuery.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/query/SearchBorrowReturnDynamicQuery.java @@ -11,6 +11,7 @@ import lombok.EqualsAndHashCode; public class SearchBorrowReturnDynamicQuery extends AbstractPageQuery { private Long goodsId; + private Long cellId; private Integer status; private Integer dynamicType; // 动态类型:0-借出 1-归还 @@ -53,6 +54,10 @@ public class SearchBorrowReturnDynamicQuery extends AbstractPageQuery { queryWrapper.eq("sog.goods_id", goodsId); } + if (cellId != null) { + queryWrapper.eq("sog.cell_id", cellId); + } + queryWrapper.eq("so.deleted", 0) .eq("sog.deleted", 0) .eq("so.pay_status", 2);