feat(product): 新增键盘商品管理模块
新增商品实体、Mapper、Service、Controller 及 VO,支持商品列表、详情、订阅等接口;同步更新 Sa-Token 放行路径与 .gitignore
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@ build/
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
/CLAUDE.md
|
||||
/AGENTS.md
|
||||
|
||||
@@ -93,7 +93,12 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
"/themes/purchase/list",
|
||||
"/themes/detail",
|
||||
"/themes/recommended",
|
||||
"/user-themes/batch-delete"
|
||||
"/user-themes/batch-delete",
|
||||
"/products/listByType",
|
||||
"/products/detail",
|
||||
"/products/inApp/list",
|
||||
"/products/subscription/list"
|
||||
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.yolo.keyborad.controller;
|
||||
|
||||
import com.yolo.keyborad.common.BaseResponse;
|
||||
import com.yolo.keyborad.common.ErrorCode;
|
||||
import com.yolo.keyborad.common.ResultUtils;
|
||||
import com.yolo.keyborad.exception.BusinessException;
|
||||
import com.yolo.keyborad.model.vo.products.KeyboardProductItemRespVO;
|
||||
import com.yolo.keyborad.service.KeyboardProductItemsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/12
|
||||
*/
|
||||
@RestController
|
||||
@Slf4j
|
||||
@RequestMapping("/products")
|
||||
@Tag(name = "商品", description = "商品相关接口")
|
||||
public class ProductsController {
|
||||
|
||||
@Resource
|
||||
private KeyboardProductItemsService productItemsService;
|
||||
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "查询商品明细", description = "根据商品ID或productId查询商品详情")
|
||||
public BaseResponse<KeyboardProductItemRespVO> getProductDetail(
|
||||
@RequestParam(value = "id", required = false) Long id,
|
||||
@RequestParam(value = "productId", required = false) String productId
|
||||
) {
|
||||
if (id == null && (productId == null || productId.isBlank())) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "id 或 productId 至少传一个");
|
||||
}
|
||||
KeyboardProductItemRespVO result = (id != null)
|
||||
? productItemsService.getProductDetailById(id)
|
||||
: productItemsService.getProductDetailByProductId(productId);
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/listByType")
|
||||
@Operation(summary = "按类型查询商品列表", description = "根据商品类型查询商品列表,type=all 返回全部")
|
||||
public BaseResponse<List<KeyboardProductItemRespVO>> listByType(@RequestParam("type") String type) {
|
||||
if (type == null || type.isBlank()) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "type 不能为空");
|
||||
}
|
||||
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType(type);
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/inApp/list")
|
||||
@Operation(summary = "查询内购商品列表", description = "查询 type=in-app-purchase 的商品列表")
|
||||
public BaseResponse<List<KeyboardProductItemRespVO>> listInAppPurchases() {
|
||||
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType("in-app-purchase");
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/subscription/list")
|
||||
@Operation(summary = "查询订阅商品列表", description = "查询 type=subscription 的商品列表")
|
||||
public BaseResponse<List<KeyboardProductItemRespVO>> listSubscriptions() {
|
||||
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType("subscription");
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.yolo.keyborad.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yolo.keyborad.model.entity.KeyboardProductItems;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/12 13:44
|
||||
*/
|
||||
|
||||
public interface KeyboardProductItemsMapper extends BaseMapper<KeyboardProductItems> {
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.yolo.keyborad.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/12 13:44
|
||||
*/
|
||||
|
||||
@Schema
|
||||
@Data
|
||||
@TableName(value = "keyboard_product_items")
|
||||
public class KeyboardProductItems {
|
||||
/**
|
||||
* 主键,自增,唯一标识每个产品项
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
@Schema(description="主键,自增,唯一标识每个产品项")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 产品标识符,唯一标识每个产品(如 com.loveKey.nyx.2month)
|
||||
*/
|
||||
@TableField(value = "product_id")
|
||||
@Schema(description="产品标识符,唯一标识每个产品(如 com.loveKey.nyx.2month)")
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 产品类型,区分订阅(subscription)和内购(in-app-purchase)
|
||||
*/
|
||||
@TableField(value = "\"type\"")
|
||||
@Schema(description="产品类型,区分订阅(subscription)和内购(in-app-purchase)")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 产品名称(如 100, 2)
|
||||
*/
|
||||
@TableField(value = "\"name\"")
|
||||
@Schema(description="产品名称(如 100, 2)")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 产品单位(如 金币,个月)
|
||||
*/
|
||||
@TableField(value = "unit")
|
||||
@Schema(description="产品单位(如 金币,个月)")
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 订阅时长的数值部分(如 2)
|
||||
*/
|
||||
@TableField(value = "duration_value")
|
||||
@Schema(description="订阅时长的数值部分(如 2)")
|
||||
private Integer durationValue;
|
||||
|
||||
/**
|
||||
* 订阅时长的单位部分(如 月,天)
|
||||
*/
|
||||
@TableField(value = "duration_unit")
|
||||
@Schema(description="订阅时长的单位部分(如 月,天)")
|
||||
private String durationUnit;
|
||||
|
||||
/**
|
||||
* 产品价格
|
||||
*/
|
||||
@TableField(value = "price")
|
||||
@Schema(description="产品价格")
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 产品的货币单位,如美元($)
|
||||
*/
|
||||
@TableField(value = "currency")
|
||||
@Schema(description="产品的货币单位,如美元($)")
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 产品的描述,提供更多细节信息
|
||||
*/
|
||||
@TableField(value = "description")
|
||||
@Schema(description="产品的描述,提供更多细节信息")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 产品项的创建时间,默认当前时间
|
||||
*/
|
||||
@TableField(value = "created_at")
|
||||
@Schema(description="产品项的创建时间,默认当前时间")
|
||||
private Date createdAt;
|
||||
|
||||
/**
|
||||
* 产品项的最后更新时间,更新时自动设置为当前时间
|
||||
*/
|
||||
@TableField(value = "updated_at")
|
||||
@Schema(description="产品项的最后更新时间,更新时自动设置为当前时间")
|
||||
private Date updatedAt;
|
||||
|
||||
/**
|
||||
* 订阅时长的具体天数
|
||||
*/
|
||||
@TableField(value = "duration_days")
|
||||
@Schema(description="订阅时长的具体天数")
|
||||
private Integer durationDays;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.yolo.keyborad.model.vo.products;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品明细返回 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "商品明细返回对象")
|
||||
public class KeyboardProductItemRespVO {
|
||||
|
||||
@Schema(description = "主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "产品标识符,如 com.loveKey.nyx.2month")
|
||||
private String productId;
|
||||
|
||||
@Schema(description = "产品类型:subscription / in-app-purchase")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "产品名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "产品单位")
|
||||
private String unit;
|
||||
|
||||
@Schema(description = "订阅时长数值")
|
||||
private Integer durationValue;
|
||||
|
||||
@Schema(description = "订阅时长单位")
|
||||
private String durationUnit;
|
||||
|
||||
@Schema(description = "订阅时长天数")
|
||||
private Integer durationDays;
|
||||
|
||||
@Schema(description = "价格")
|
||||
private BigDecimal price;
|
||||
|
||||
@Schema(description = "货币单位")
|
||||
private String currency;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.yolo.keyborad.service;
|
||||
|
||||
import com.yolo.keyborad.model.entity.KeyboardProductItems;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yolo.keyborad.model.vo.products.KeyboardProductItemRespVO;
|
||||
import java.util.List;
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/12 13:44
|
||||
*/
|
||||
|
||||
public interface KeyboardProductItemsService extends IService<KeyboardProductItems>{
|
||||
|
||||
/**
|
||||
* 根据主键ID查询商品明细
|
||||
*
|
||||
* @param id 商品主键ID
|
||||
* @return 商品明细(不存在返回 null)
|
||||
*/
|
||||
KeyboardProductItemRespVO getProductDetailById(Long id);
|
||||
|
||||
/**
|
||||
* 根据 Apple productId 查询商品明细
|
||||
*
|
||||
* @param productId 商品 productId
|
||||
* @return 商品明细(不存在返回 null)
|
||||
*/
|
||||
KeyboardProductItemRespVO getProductDetailByProductId(String productId);
|
||||
|
||||
/**
|
||||
* 根据商品类型查询商品列表
|
||||
* type=all 时返回全部
|
||||
*
|
||||
* @param type 商品类型:subscription / in-app-purchase / all
|
||||
* @return 商品列表
|
||||
*/
|
||||
List<KeyboardProductItemRespVO> listProductsByType(String type);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.yolo.keyborad.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.yolo.keyborad.model.vo.products.KeyboardProductItemRespVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yolo.keyborad.model.entity.KeyboardProductItems;
|
||||
import com.yolo.keyborad.mapper.KeyboardProductItemsMapper;
|
||||
import com.yolo.keyborad.service.KeyboardProductItemsService;
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/12 13:44
|
||||
*/
|
||||
|
||||
@Service
|
||||
public class KeyboardProductItemsServiceImpl extends ServiceImpl<KeyboardProductItemsMapper, KeyboardProductItems> implements KeyboardProductItemsService{
|
||||
|
||||
|
||||
/**
|
||||
* 根据ID获取产品详情
|
||||
*
|
||||
* @param id 产品ID
|
||||
* @return 产品详情响应对象,如果ID为空或未找到产品则返回null
|
||||
*/
|
||||
@Override
|
||||
public KeyboardProductItemRespVO getProductDetailById(Long id) {
|
||||
// 参数校验:ID不能为空
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据ID查询产品信息
|
||||
KeyboardProductItems item = this.getById(id);
|
||||
|
||||
// 将实体对象转换为响应VO对象并返回
|
||||
return item == null ? null : BeanUtil.copyProperties(item, KeyboardProductItemRespVO.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据产品ID获取产品详情
|
||||
*
|
||||
* @param productId 产品ID
|
||||
* @return 产品详情响应对象,如果产品ID为空或未找到产品则返回null
|
||||
*/
|
||||
@Override
|
||||
public KeyboardProductItemRespVO getProductDetailByProductId(String productId) {
|
||||
// 参数校验:产品ID不能为空
|
||||
if (productId == null || productId.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据产品ID查询产品信息
|
||||
KeyboardProductItems item = this.lambdaQuery()
|
||||
.eq(KeyboardProductItems::getProductId, productId)
|
||||
.one();
|
||||
|
||||
// 将实体对象转换为响应VO对象并返回
|
||||
return item == null ? null : BeanUtil.copyProperties(item, KeyboardProductItemRespVO.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据类型获取产品列表
|
||||
*
|
||||
* @param type 产品类型,如果为null、空字符串或"all"则查询所有产品
|
||||
* @return 产品详情响应列表,按ID升序排列
|
||||
*/
|
||||
@Override
|
||||
public java.util.List<KeyboardProductItemRespVO> listProductsByType(String type) {
|
||||
// 创建Lambda查询构造器
|
||||
var query = this.lambdaQuery();
|
||||
|
||||
// 如果类型参数有效且不是"all",则添加类型过滤条件
|
||||
if (type != null && !type.isBlank() && !"all".equalsIgnoreCase(type)) {
|
||||
query.eq(KeyboardProductItems::getType, type);
|
||||
}
|
||||
|
||||
// 执行查询,按ID升序排列
|
||||
java.util.List<KeyboardProductItems> items = query
|
||||
.orderByAsc(KeyboardProductItems::getId)
|
||||
.list();
|
||||
|
||||
// 将实体对象转换为响应VO对象并返回
|
||||
return items.stream()
|
||||
.map(i -> BeanUtil.copyProperties(i, KeyboardProductItemRespVO.class))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
26
src/main/resources/mapper/KeyboardProductItemsMapper.xml
Normal file
26
src/main/resources/mapper/KeyboardProductItemsMapper.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yolo.keyborad.mapper.KeyboardProductItemsMapper">
|
||||
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardProductItems">
|
||||
<!--@mbg.generated-->
|
||||
<!--@Table keyboard_product_items-->
|
||||
<id column="id" jdbcType="BIGINT" property="id" />
|
||||
<result column="product_id" jdbcType="VARCHAR" property="productId" />
|
||||
<result column="type" jdbcType="VARCHAR" property="type" />
|
||||
<result column="name" jdbcType="VARCHAR" property="name" />
|
||||
<result column="unit" jdbcType="VARCHAR" property="unit" />
|
||||
<result column="duration_value" jdbcType="INTEGER" property="durationValue" />
|
||||
<result column="duration_unit" jdbcType="VARCHAR" property="durationUnit" />
|
||||
<result column="price" jdbcType="NUMERIC" property="price" />
|
||||
<result column="currency" jdbcType="VARCHAR" property="currency" />
|
||||
<result column="description" jdbcType="VARCHAR" property="description" />
|
||||
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
|
||||
<result column="duration_days" jdbcType="INTEGER" property="durationDays" />
|
||||
</resultMap>
|
||||
<sql id="Base_Column_List">
|
||||
<!--@mbg.generated-->
|
||||
id, product_id, "type", "name", unit, duration_value, duration_unit, price, currency,
|
||||
description, created_at, updated_at, duration_days
|
||||
</sql>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user