This commit is contained in:
Guoqs
2026-03-04 16:06:55 +08:00
parent 100ca67ad6
commit c231c322f8
21 changed files with 1766 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
package com.jsowell.api.uniapp.customer;
import com.github.pagehelper.PageHelper;
import com.jsowell.common.core.controller.BaseController;
import com.jsowell.common.core.domain.AjaxResult;
import com.jsowell.pile.domain.CouponTemplate;
import com.jsowell.pile.domain.MemberCoupon;
import com.jsowell.pile.service.MemberCouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 优惠券用户端 API小程序/App
*/
@Api(tags = "优惠券-用户端")
@RestController
@RequestMapping("/coupon")
public class CouponController extends BaseController {
@Autowired
private MemberCouponService memberCouponService;
/** 二维码签名密钥,配置在 application.yml */
@Value("${coupon.qrcode.secret:coupon_qrcode_secret}")
private String qrcodeSecret;
/** 二维码 token 有效期(秒),默认 5 分钟 */
private static final int QRCODE_TOKEN_TTL_SECONDS = 300;
/**
* 获取可兑换券模板列表
*/
@ApiOperation("可兑换券列表")
@GetMapping("/template/list")
public AjaxResult templateList(
@ApiParam("会员ID") @RequestParam String memberId,
@ApiParam("当前所在站点ID用于 scope 过滤") @RequestParam(required = false) Long stationId,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<CouponTemplate> list = memberCouponService.listAvailableTemplates(memberId, stationId);
return AjaxResult.success(list);
}
/**
* 积分兑换券
*/
@ApiOperation("积分兑换券")
@PostMapping("/exchange")
public AjaxResult exchange(
@ApiParam("会员ID") @RequestParam String memberId,
@ApiParam("模板ID") @RequestParam Long templateId,
@ApiParam("客户端幂等键UUID") @RequestParam String requestId) {
try {
MemberCoupon coupon = memberCouponService.exchange(memberId, templateId, requestId);
return AjaxResult.success(coupon);
} catch (Exception e) {
logger.error("兑换失败memberId: {}, templateId: {}", memberId, templateId, e);
return AjaxResult.error(e.getMessage());
}
}
/**
* 我的券包
*/
@ApiOperation("我的券包列表")
@GetMapping("/my/list")
public AjaxResult myList(
@RequestParam String memberId,
@ApiParam("状态0=未使用 1=已使用 2=已过期,不传=全部") @RequestParam(required = false) Integer status,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<MemberCoupon> list = memberCouponService.myList(memberId, status);
return AjaxResult.success(list);
}
/**
* 券详情(含核销二维码短时 token
*/
@ApiOperation("券详情含二维码token")
@GetMapping("/my/detail/{couponNo}")
public AjaxResult detail(
@RequestParam String memberId,
@PathVariable String couponNo) {
try {
MemberCoupon coupon = memberCouponService.detail(memberId, couponNo);
String qrcodeToken = generateQrcodeToken(couponNo);
Map<String, Object> result = new HashMap<>();
result.put("coupon", coupon);
result.put("qrcodeToken", qrcodeToken);
result.put("qrcodeExpireSeconds", QRCODE_TOKEN_TTL_SECONDS);
return AjaxResult.success(result);
} catch (Exception e) {
logger.error("获取券详情失败couponNo: {}", couponNo, e);
return AjaxResult.error(e.getMessage());
}
}
// ----------------------------------------------------------------
// 私有:生成短时签名 token格式couponNo.expireTs.hmac
// ----------------------------------------------------------------
private String generateQrcodeToken(String couponNo) {
try {
long expireTs = System.currentTimeMillis() / 1000 + QRCODE_TOKEN_TTL_SECONDS;
String payload = couponNo + "." + expireTs;
String hmac = hmacSha256(payload, qrcodeSecret);
return Base64.getUrlEncoder().withoutPadding()
.encodeToString((payload + "." + hmac).getBytes());
} catch (Exception e) {
logger.error("生成二维码token失败", e);
throw new RuntimeException("生成二维码失败");
}
}
private String hmacSha256(String data, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
return Base64.getUrlEncoder().withoutPadding()
.encodeToString(mac.doFinal(data.getBytes("UTF-8")));
}
}

View File

@@ -0,0 +1,72 @@
package com.jsowell.web.controller.pile;
import com.github.pagehelper.PageHelper;
import com.jsowell.common.core.controller.BaseController;
import com.jsowell.common.core.domain.AjaxResult;
import com.jsowell.common.core.page.TableDataInfo;
import com.jsowell.pile.domain.MemberCoupon;
import com.jsowell.pile.service.MemberCouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 优惠券兑换记录 & 核销 Controller管理后台
*/
@Api(tags = "优惠券兑换记录管理")
@RestController
@RequestMapping("/admin/coupon")
public class CouponRecordController extends BaseController {
@Autowired
private MemberCouponService memberCouponService;
/**
* 兑换记录列表
*/
@ApiOperation("兑换记录查询")
@PreAuthorize("@ss.hasPermi('coupon:record:list')")
@GetMapping("/record/list")
public TableDataInfo recordList(MemberCoupon query,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<MemberCoupon> list = memberCouponService.listRecordForAdmin(query);
return getDataTable(list);
}
/**
* 核销券
*/
@ApiOperation("核销券")
@PreAuthorize("@ss.hasPermi('coupon:record:verify')")
@PostMapping("/verify")
public AjaxResult verify(@RequestParam String couponNo,
@RequestParam Long storeId,
@RequestParam(required = false) String requestId,
@RequestParam(required = false) String operatorId) {
try {
memberCouponService.verify(couponNo, storeId, requestId, operatorId);
return AjaxResult.success("核销成功");
} catch (Exception e) {
logger.error("核销失败couponNo: {}", couponNo, e);
return AjaxResult.error(e.getMessage());
}
}
/**
* 人工作废券
*/
@ApiOperation("人工作废券")
@PreAuthorize("@ss.hasPermi('coupon:record:invalidate')")
@PostMapping("/invalidate")
public AjaxResult invalidate(@RequestParam String couponNo,
@RequestParam String reason) {
// TODO: 实现人工作废逻辑(需补充 MemberCouponMapper.invalidate + 审计日志)
return AjaxResult.error("功能待实现");
}
}

View File

@@ -0,0 +1,109 @@
package com.jsowell.web.controller.pile;
import com.github.pagehelper.PageHelper;
import com.jsowell.common.core.controller.BaseController;
import com.jsowell.common.core.domain.AjaxResult;
import com.jsowell.common.core.page.TableDataInfo;
import com.jsowell.pile.domain.CouponTemplate;
import com.jsowell.pile.service.CouponTemplateService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 优惠券模板管理 Controller管理后台
*/
@Api(tags = "优惠券模板管理")
@RestController
@RequestMapping("/admin/coupon/template")
public class CouponTemplateController extends BaseController {
@Autowired
private CouponTemplateService couponTemplateService;
/**
* 列表查询
* 运营商管理员调用时,由前端传入 creatorMerchantId后端也应从登录上下文二次校验
*/
@ApiOperation("券模板列表")
@PreAuthorize("@ss.hasPermi('coupon:template:list')")
@GetMapping("/list")
public TableDataInfo list(CouponTemplate query,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<CouponTemplate> list = couponTemplateService.listForAdmin(query);
return getDataTable(list);
}
/**
* 详情
*/
@ApiOperation("券模板详情")
@PreAuthorize("@ss.hasPermi('coupon:template:query')")
@GetMapping("/{id}")
public AjaxResult getById(@PathVariable Long id) {
return AjaxResult.success(couponTemplateService.getById(id));
}
/**
* 新增券模板
* isPlatformAdmin / loginMerchantId 从登录上下文获取(此处简化演示,实际需从 SecurityUtils 取)
*/
@ApiOperation("新增券模板")
@PreAuthorize("@ss.hasPermi('coupon:template:add')")
@PostMapping("/add")
public AjaxResult add(@RequestBody CouponTemplate template,
@ApiParam("是否平台管理员") @RequestParam(defaultValue = "false") boolean isPlatformAdmin,
@ApiParam("登录运营商ID运营商管理员必传") @RequestParam(required = false) Long loginMerchantId) {
try {
couponTemplateService.add(template, loginMerchantId, isPlatformAdmin);
return AjaxResult.success("新增成功");
} catch (Exception e) {
logger.error("新增券模板失败", e);
return AjaxResult.error(e.getMessage());
}
}
/**
* 编辑券模板
*/
@ApiOperation("编辑券模板")
@PreAuthorize("@ss.hasPermi('coupon:template:edit')")
@PutMapping("/edit")
public AjaxResult edit(@RequestBody CouponTemplate template,
@RequestParam(defaultValue = "false") boolean isPlatformAdmin,
@RequestParam(required = false) Long loginMerchantId) {
try {
couponTemplateService.edit(template, loginMerchantId, isPlatformAdmin);
return AjaxResult.success("编辑成功");
} catch (Exception e) {
logger.error("编辑券模板失败", e);
return AjaxResult.error(e.getMessage());
}
}
/**
* 上下架
*/
@ApiOperation("上下架券模板")
@PreAuthorize("@ss.hasPermi('coupon:template:edit')")
@PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestParam Long id,
@ApiParam("0=下架 1=上架") @RequestParam Integer status,
@RequestParam(defaultValue = "false") boolean isPlatformAdmin,
@RequestParam(required = false) Long loginMerchantId) {
try {
couponTemplateService.changeStatus(id, status, loginMerchantId, isPlatformAdmin);
return AjaxResult.success();
} catch (Exception e) {
logger.error("修改券模板状态失败", e);
return AjaxResult.error(e.getMessage());
}
}
}