feat(订单): 添加商品封面图片和审批图片字段

feat(查询): 增加按货柜ID查询订单动态功能

feat(企业微信): 新增手动同步企业微信数据接口
This commit is contained in:
dzq 2025-11-29 17:22:36 +08:00
parent 5a826a7777
commit ca7a4b53ab
4 changed files with 348 additions and 0 deletions

View File

@ -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<String> 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<String> 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<String> 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<QyAuthCorpInfoEntity> 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<String> 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<QyAuthCorpInfoEntity> 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<String> 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<String> 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<String> 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<QyAuthCorpInfoEntity> 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<QyAuthCorpInfoEntity> 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<List<String>> getAppidList() {
try {
List<String> 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<List<QyAuthCorpInfoEntity>> getCorpList(
@Parameter(description = "应用ID", required = true)
@RequestParam String appid) {
try {
List<QyAuthCorpInfoEntity> 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()));
}
}
}

View File

@ -86,6 +86,7 @@ public interface ShopOrderMapper extends BaseMapper<ShopOrderEntity> {
"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<ShopOrderEntity> {
"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<ShopOrderEntity> {
"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<ShopOrderEntity> {
"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 " +

View File

@ -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) {

View File

@ -11,6 +11,7 @@ import lombok.EqualsAndHashCode;
public class SearchBorrowReturnDynamicQuery<T> extends AbstractPageQuery<T> {
private Long goodsId;
private Long cellId;
private Integer status;
private Integer dynamicType; // 动态类型0-借出 1-归还
@ -53,6 +54,10 @@ public class SearchBorrowReturnDynamicQuery<T> extends AbstractPageQuery<T> {
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);