package com.doumee.api.business;
|
|
import com.alibaba.fastjson.JSON;
|
import com.doumee.api.BaseController;
|
import com.doumee.core.annotation.pr.PreventRepeat;
|
import com.doumee.core.constants.Constants;
|
import com.doumee.core.douyin.DouyinClient;
|
import com.doumee.core.douyin.dto.DouyinBaseResp;
|
import com.doumee.core.douyin.dto.DouyinCancelParam;
|
import com.doumee.core.douyin.dto.DouyinShopPoiResp;
|
import com.doumee.core.model.ApiResponse;
|
import com.doumee.core.model.LoginUserInfo;
|
import com.doumee.core.model.PageData;
|
import com.doumee.core.model.PageWrap;
|
import com.doumee.core.utils.ID;
|
import com.doumee.dao.business.model.DouyinVerifyLog;
|
import com.doumee.dao.business.model.DouyinVerifyRecord;
|
import com.doumee.dao.business.vo.DouyinVerifyRecordPageVO;
|
import com.doumee.service.business.DouyinVerifyLogService;
|
import com.doumee.service.business.DouyinVerifyService;
|
import io.swagger.annotations.Api;
|
import io.swagger.annotations.ApiOperation;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.stream.Collectors;
|
|
/**
|
* 抖音券核销(管理端):核销记录对外分页 + 撤销核销。
|
* <p>撤销核销由 web 端(/web/douyin/cancel)迁移至此,鉴权由 JWT 改为 Shiro;
|
* 管理端为运营补救场景,不受「核销后1小时内」限制。每次撤销落 douyin_verify_log 审计。
|
*
|
* @author rk
|
* @date 2026/06/26
|
*/
|
@Slf4j
|
@Api(tags = "抖音券核销")
|
@RestController
|
@RequestMapping("/business/douyinVerify")
|
public class DouyinVerifyController extends BaseController {
|
|
@Autowired
|
private DouyinVerifyService douyinVerifyService;
|
@Autowired
|
private DouyinVerifyLogService douyinVerifyLogService;
|
/** 抖音 HTTP 客户端:门店列表查询为无状态透传,直接调抖音,不经 Service */
|
@Autowired
|
private DouyinClient douyinClient;
|
|
@ApiOperation("核销记录分页(对外)")
|
@PostMapping("/page")
|
@RequiresPermissions("business:douyinVerify:query")
|
public ApiResponse<PageData<DouyinVerifyRecordPageVO>> findPage(@RequestBody PageWrap<DouyinVerifyRecordPageVO> pageWrap) {
|
return ApiResponse.success(douyinVerifyService.findManagePage(pageWrap));
|
}
|
|
@ApiOperation("查询抖音商户下门店(用于选核销门店;account_id 从字典读取)")
|
@PostMapping("/poiList")
|
@RequiresPermissions("business:douyinVerify:query")
|
public ApiResponse<List<String>> poiList() {
|
// 门店查询为无状态透传(无落库),Controller → DouyinClient 直连;account_id 由 Client 从字典读取
|
DouyinBaseResp<DouyinShopPoiResp> resp = douyinClient.shopPoiQuery();
|
List<DouyinShopPoiResp.Poi> pois = resp == null || resp.getData() == null ? null : resp.getData().getPois();
|
if (pois == null || pois.isEmpty()) {
|
return ApiResponse.success(Collections.emptyList());
|
}
|
// 仅提取门店ID,过滤 poi 节点或 poiId 为空的条目
|
List<String> poiIds = pois.stream()
|
.filter(p -> p != null && p.getPoi() != null && StringUtils.isNotBlank(p.getPoi().getPoiId()))
|
.map(p -> p.getPoi().getPoiId())
|
.collect(Collectors.toList());
|
return ApiResponse.success(poiIds);
|
}
|
|
@PreventRepeat
|
@ApiOperation("撤销核销(管理端,不受1小时限制;成功后作废本地套餐卡)")
|
@PostMapping("/cancel")
|
@RequiresPermissions("business:douyinVerify:cancel")
|
public ApiResponse<DouyinVerifyRecord> cancel(@RequestBody DouyinCancelParam param, HttpServletRequest request) {
|
long start = System.currentTimeMillis();
|
DouyinVerifyLog opLog = baseLog(Constants.DOUYIN_VERIFY_OPERATE_TYPE.CANCEL.getKey(), "/business/douyinVerify/cancel", start, request);
|
opLog.setRawRequest(JSON.toJSONString(param));
|
try {
|
// 操作人取 Shiro 登录用户 id(管理端管理员,非会员)
|
LoginUserInfo loginUser = getLoginUser();
|
String operator = loginUser == null ? null : loginUser.getId();
|
DouyinVerifyRecord rec = douyinVerifyService.cancel(param, operator);
|
fillByRecord(opLog, rec);
|
return ApiResponse.success(rec);
|
} catch (Throwable e) {
|
opLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
|
opLog.setErrorMsg(e.getMessage());
|
throw e;
|
} finally {
|
saveLog(opLog);
|
}
|
}
|
|
// ---------------- 撤销操作日志辅助 ----------------
|
|
/** 构造一条撤销操作日志骨架(主键/类型/路径/IP/耗时/时间) */
|
private DouyinVerifyLog baseLog(int operateType, String apiPath, long start, HttpServletRequest request) {
|
DouyinVerifyLog l = new DouyinVerifyLog();
|
l.setId(ID.nextGUID());
|
l.setOperateType(operateType);
|
l.setApiPath(apiPath);
|
// 管理端操作非会员,memberId 留空
|
l.setIp(request.getRemoteAddr());
|
l.setCostMs((int) (System.currentTimeMillis() - start));
|
l.setCreateDate(new Date());
|
l.setIsdeleted(Constants.ZERO);
|
return l;
|
}
|
|
/** 撤销成功后,用核销记录回填日志的业务字段与结果 */
|
private void fillByRecord(DouyinVerifyLog opLog, DouyinVerifyRecord rec) {
|
if (rec == null) {
|
opLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
|
return;
|
}
|
opLog.setVerifyRecordId(rec.getId());
|
opLog.setOriginCode(rec.getOriginCode());
|
if (StringUtils.isNotBlank(rec.getPoiId())) {
|
opLog.setPoiId(rec.getPoiId());
|
}
|
opLog.setRawResponse(rec.getRawResponse());
|
opLog.setResult(Constants.equalsInteger(rec.getCancelStatus(), Constants.DOUYIN_VERIFY_CANCEL_STATUS.DONE.getKey()) ? Constants.DOUYIN_VERIFY_LOG_RESULT.SUCCESS.getKey() : Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
|
opLog.setErrorMsg(rec.getCancelMsg());
|
}
|
|
/** 落库操作日志;日志自身异常不抛出,避免影响主流程 */
|
private void saveLog(DouyinVerifyLog opLog) {
|
try {
|
douyinVerifyLogService.record(opLog);
|
} catch (Exception e) {
|
log.warn("记录抖音撤销操作日志失败 type={}, recordId={}", opLog.getOperateType(), opLog.getVerifyRecordId(), e);
|
}
|
}
|
}
|