Compare commits
10 Commits
5227b81acb
...
e8ef359fcf
| Author | SHA1 | Date | |
|---|---|---|---|
| e8ef359fcf | |||
| 567a8bf165 | |||
| f937b03940 | |||
| 262c822585 | |||
| 77e8e9a2a7 | |||
| 03dc005b38 | |||
| 1a6fb944b2 | |||
| 22b97b99aa | |||
| 4f56541913 | |||
| 0d1545f568 |
@@ -46,7 +46,11 @@ public enum ErrorCode {
|
|||||||
REPEATEDLY_ADDING_CHARACTER(50009, "重复添加键盘人设"),
|
REPEATEDLY_ADDING_CHARACTER(50009, "重复添加键盘人设"),
|
||||||
MAIL_SEND_BUSY(50010,"邮件发送频繁,1分钟后再试" ),
|
MAIL_SEND_BUSY(50010,"邮件发送频繁,1分钟后再试" ),
|
||||||
PASSWORD_CAN_NOT_NULL(50011, "密码不能为空" ),
|
PASSWORD_CAN_NOT_NULL(50011, "密码不能为空" ),
|
||||||
USER_HAS_EXISTED(50012, "用户已存在" );
|
USER_HAS_EXISTED(50012, "用户已存在" ),
|
||||||
|
INSUFFICIENT_BALANCE(50013, "余额不足"),
|
||||||
|
THEME_NOT_FOUND(40410, "主题不存在"),
|
||||||
|
THEME_ALREADY_PURCHASED(50014, "主题已购买"),
|
||||||
|
THEME_NOT_AVAILABLE(50015, "主题不可购买");
|
||||||
/**
|
/**
|
||||||
* 状态码
|
* 状态码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -86,7 +86,13 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
|||||||
"/user/resetPassWord",
|
"/user/resetPassWord",
|
||||||
"/chat/talk",
|
"/chat/talk",
|
||||||
"/chat/save_embed",
|
"/chat/save_embed",
|
||||||
"/themes/listByStyle"
|
"/themes/listByStyle",
|
||||||
|
"/wallet/balance",
|
||||||
|
"/themes/purchase",
|
||||||
|
"/themes/purchased",
|
||||||
|
"/themes/purchase/list",
|
||||||
|
"/themes/detail",
|
||||||
|
"/themes/recommended"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
package com.yolo.keyborad.controller;
|
package com.yolo.keyborad.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.yolo.keyborad.common.BaseResponse;
|
import com.yolo.keyborad.common.BaseResponse;
|
||||||
import com.yolo.keyborad.common.ResultUtils;
|
import com.yolo.keyborad.common.ResultUtils;
|
||||||
|
import com.yolo.keyborad.model.dto.purchase.ThemePurchaseReq;
|
||||||
|
import com.yolo.keyborad.model.vo.purchase.ThemePurchaseListRespVO;
|
||||||
|
import com.yolo.keyborad.model.vo.purchase.ThemePurchaseRespVO;
|
||||||
import com.yolo.keyborad.model.vo.themes.KeyboardThemeStylesRespVO;
|
import com.yolo.keyborad.model.vo.themes.KeyboardThemeStylesRespVO;
|
||||||
import com.yolo.keyborad.model.vo.themes.KeyboardThemesRespVO;
|
import com.yolo.keyborad.model.vo.themes.KeyboardThemesRespVO;
|
||||||
|
import com.yolo.keyborad.service.KeyboardThemePurchaseService;
|
||||||
import com.yolo.keyborad.service.KeyboardThemeStylesService;
|
import com.yolo.keyborad.service.KeyboardThemeStylesService;
|
||||||
import com.yolo.keyborad.service.KeyboardThemesService;
|
import com.yolo.keyborad.service.KeyboardThemesService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
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;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -33,12 +35,15 @@ public class ThemesController {
|
|||||||
@Resource
|
@Resource
|
||||||
private KeyboardThemeStylesService keyboardThemeStylesService;
|
private KeyboardThemeStylesService keyboardThemeStylesService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardThemePurchaseService themePurchaseService;
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/listByStyle")
|
@GetMapping("/listByStyle")
|
||||||
@Operation(summary = "按风格查询主题", description = "按主题风格查询主题列表接口")
|
@Operation(summary = "按风格查询主题", description = "按主题风格查询主题列表接口")
|
||||||
public BaseResponse<List<KeyboardThemesRespVO>> listByStyle(@RequestParam("themeStyle") Long themeStyleId) {
|
public BaseResponse<List<KeyboardThemesRespVO>> listByStyle(@RequestParam("themeStyle") Long themeStyleId) {
|
||||||
return ResultUtils.success(themesService.selectThemesByStyle(themeStyleId));
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
}
|
return ResultUtils.success(themesService.selectThemesByStyle(themeStyleId,userId));}
|
||||||
|
|
||||||
@GetMapping("/listAllStyles")
|
@GetMapping("/listAllStyles")
|
||||||
@Operation(summary = "查询所有主题风格", description = "查询所有主题风格列表接口")
|
@Operation(summary = "查询所有主题风格", description = "查询所有主题风格列表接口")
|
||||||
@@ -46,5 +51,44 @@ public class ThemesController {
|
|||||||
return ResultUtils.success(keyboardThemeStylesService.selectAllThemeStyles());
|
return ResultUtils.success(keyboardThemeStylesService.selectAllThemeStyles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/purchase")
|
||||||
|
@Operation(summary = "购买主题", description = "购买主题接口,扣减用户余额")
|
||||||
|
public BaseResponse<ThemePurchaseRespVO> purchaseTheme(@RequestBody ThemePurchaseReq req) {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
ThemePurchaseRespVO result = themePurchaseService.purchaseTheme(userId, req.getThemeId());
|
||||||
|
return ResultUtils.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/purchase/list")
|
||||||
|
@Operation(summary = "查询购买记录", description = "查询当前用户的主题购买记录")
|
||||||
|
public BaseResponse<List<ThemePurchaseListRespVO>> getPurchaseList() {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
List<ThemePurchaseListRespVO> result = themePurchaseService.getUserPurchaseList(userId);
|
||||||
|
return ResultUtils.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/purchased")
|
||||||
|
@Operation(summary = "查询已购买的主题", description = "查询当前用户已购买的主题列表")
|
||||||
|
public BaseResponse<List<KeyboardThemesRespVO>> getPurchasedThemes() {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
List<KeyboardThemesRespVO> result = themePurchaseService.getUserPurchasedThemes(userId);
|
||||||
|
return ResultUtils.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@Operation(summary = "查询主题详情", description = "根据主题ID查询主题详情")
|
||||||
|
public BaseResponse<KeyboardThemesRespVO> getThemeDetail(@RequestParam Long themeId) {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
KeyboardThemesRespVO result = themesService.getThemeDetail(themeId, userId);
|
||||||
|
return ResultUtils.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/recommended")
|
||||||
|
@Operation(summary = "推荐主题列表", description = "按真实下载数量降序返回推荐主题")
|
||||||
|
public BaseResponse<List<KeyboardThemesRespVO>> getRecommendedThemes() {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
List<KeyboardThemesRespVO> result = themesService.getRecommendedThemes(userId);
|
||||||
|
return ResultUtils.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.yolo.keyborad.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.yolo.keyborad.common.BaseResponse;
|
||||||
|
import com.yolo.keyborad.common.ResultUtils;
|
||||||
|
import com.yolo.keyborad.model.dto.usertheme.BatchDeleteUserThemesReq;
|
||||||
|
import com.yolo.keyborad.service.KeyboardUserThemesService;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/11
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
@RequestMapping("/user-themes")
|
||||||
|
@Tag(name = "用户主题")
|
||||||
|
public class UserThemesController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardUserThemesService userThemesService;
|
||||||
|
|
||||||
|
@PostMapping("/batch-delete")
|
||||||
|
@Operation(summary = "批量删除用户主题", description = "批量逻辑删除用户主题")
|
||||||
|
public BaseResponse<Boolean> batchDeleteUserThemes(@RequestBody BatchDeleteUserThemesReq req) {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
userThemesService.batchDeleteUserThemes(userId, req.getThemeIds());
|
||||||
|
return ResultUtils.success(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.yolo.keyborad.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.yolo.keyborad.common.BaseResponse;
|
||||||
|
import com.yolo.keyborad.common.ResultUtils;
|
||||||
|
import com.yolo.keyborad.model.vo.wallet.KeyboardUserWalletRespVO;
|
||||||
|
import com.yolo.keyborad.service.KeyboardUserWalletService;
|
||||||
|
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.RestController;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
@RequestMapping("/wallet")
|
||||||
|
@Tag(name = "钱包", description = "用户钱包接口")
|
||||||
|
public class WalletController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardUserWalletService walletService;
|
||||||
|
|
||||||
|
@GetMapping("/balance")
|
||||||
|
@Operation(summary = "查询钱包余额", description = "查询当前登录用户的钱包余额")
|
||||||
|
public BaseResponse<KeyboardUserWalletRespVO> getBalance() {
|
||||||
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
KeyboardUserWalletRespVO balance = walletService.getWalletBalance(userId);
|
||||||
|
return ResultUtils.success(balance);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.yolo.keyborad.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardThemePurchase;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 19:17
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardThemePurchaseMapper extends BaseMapper<KeyboardThemePurchase> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.yolo.keyborad.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserThemes;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/11 13:31
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardUserThemesMapper extends BaseMapper<KeyboardUserThemes> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.yolo.keyborad.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserWallet;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 18:18
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardUserWalletMapper extends BaseMapper<KeyboardUserWallet> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.yolo.keyborad.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardWalletTransaction;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 18:54
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardWalletTransactionMapper extends BaseMapper<KeyboardWalletTransaction> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.yolo.keyborad.model.dto.purchase;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题购买请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ThemePurchaseReq {
|
||||||
|
|
||||||
|
@Schema(description = "主题ID")
|
||||||
|
private Long themeId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.yolo.keyborad.model.dto.usertheme;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/11
|
||||||
|
*/
|
||||||
|
@Schema(description = "批量删除用户主题请求")
|
||||||
|
@Data
|
||||||
|
public class BatchDeleteUserThemesReq {
|
||||||
|
/**
|
||||||
|
* 主题ID列表
|
||||||
|
*/
|
||||||
|
@Schema(description = "主题ID列表")
|
||||||
|
private List<Long> themeIds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
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/10 19:17
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 皮肤购买记录表(积分支付)
|
||||||
|
*/
|
||||||
|
@Schema(description="皮肤购买记录表(积分支付)")
|
||||||
|
@Data
|
||||||
|
@TableName(value = "keyboard_theme_purchase")
|
||||||
|
public class KeyboardThemePurchase {
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@Schema(description="")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务订单号
|
||||||
|
*/
|
||||||
|
@TableField(value = "order_no")
|
||||||
|
@Schema(description="业务订单号")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 购买用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
@Schema(description="购买用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题皮肤ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "theme_id")
|
||||||
|
@Schema(description="主题皮肤ID")
|
||||||
|
private Long themeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 皮肤原始所需积分
|
||||||
|
*/
|
||||||
|
@TableField(value = "cost_points")
|
||||||
|
@Schema(description="皮肤原始所需积分")
|
||||||
|
private BigDecimal costPoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际扣除积分
|
||||||
|
*/
|
||||||
|
@TableField(value = "paid_points")
|
||||||
|
@Schema(description="实际扣除积分")
|
||||||
|
private BigDecimal paidPoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付状态:0待支付 1已支付 2已关闭 3已退款
|
||||||
|
*/
|
||||||
|
@TableField(value = "pay_status")
|
||||||
|
@Schema(description="支付状态:0待支付 1已支付 2已关闭 3已退款")
|
||||||
|
private Short payStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的积分扣费流水ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "wallet_tx_id")
|
||||||
|
@Schema(description="关联的积分扣费流水ID")
|
||||||
|
private Long walletTxId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已退款的积分数量
|
||||||
|
*/
|
||||||
|
@TableField(value = "refund_points")
|
||||||
|
@Schema(description="已退款的积分数量")
|
||||||
|
private Integer refundPoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 积分退款完成时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "refunded_at")
|
||||||
|
@Schema(description="积分退款完成时间")
|
||||||
|
private Date refundedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "created_at")
|
||||||
|
@Schema(description="创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付完成时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "paid_at")
|
||||||
|
@Schema(description="支付完成时间")
|
||||||
|
private Date paidAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "updated_at")
|
||||||
|
@Schema(description="更新时间")
|
||||||
|
private Date updatedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注或扩展信息
|
||||||
|
*/
|
||||||
|
@TableField(value = "remark")
|
||||||
|
@Schema(description="备注或扩展信息")
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import com.yolo.keyborad.typehandler.StringArrayTypeHandler;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -31,9 +33,9 @@ public class KeyboardThemes {
|
|||||||
@Schema(description="键盘价格")
|
@Schema(description="键盘价格")
|
||||||
private BigDecimal themePrice;
|
private BigDecimal themePrice;
|
||||||
|
|
||||||
@TableField(value = "theme_tag", typeHandler = StringArrayTypeHandler.class)
|
@TableField(value = "theme_tag")
|
||||||
@Schema(description="主题标签")
|
@Schema(description="主题标签")
|
||||||
private String[] themeTag;
|
private List<ThemeTagItem> themeTag;
|
||||||
|
|
||||||
@TableField(value = "theme_download")
|
@TableField(value = "theme_download")
|
||||||
@Schema(description="主题下载次数")
|
@Schema(description="主题下载次数")
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
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.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/11 13:31
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Schema
|
||||||
|
@Data
|
||||||
|
@TableName(value = "keyboard_user_themes")
|
||||||
|
public class KeyboardUserThemes {
|
||||||
|
/**
|
||||||
|
* 主键 id
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@Schema(description="主键 id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题主键
|
||||||
|
*/
|
||||||
|
@TableField(value = "theme_id")
|
||||||
|
@Schema(description="主题主键")
|
||||||
|
private Long themeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 Id
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
@Schema(description="用户 Id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "created_at")
|
||||||
|
@Schema(description="创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否从显示移除
|
||||||
|
*/
|
||||||
|
@TableField(value = "view_deleted")
|
||||||
|
@Schema(description="是否从显示移除")
|
||||||
|
private Boolean viewDeleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "updated_at")
|
||||||
|
@Schema(description="更新时间")
|
||||||
|
private Boolean updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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/10 18:18
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Schema
|
||||||
|
@Data
|
||||||
|
@TableName(value = "keyboard_user_wallet")
|
||||||
|
public class KeyboardUserWallet {
|
||||||
|
/**
|
||||||
|
* 主键 Id
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@Schema(description = "主键 Id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 id
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
@Schema(description = "用户 id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 余额
|
||||||
|
*/
|
||||||
|
@TableField(value = "balance")
|
||||||
|
@Schema(description = "余额")
|
||||||
|
private BigDecimal balance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乐观锁版本
|
||||||
|
*/
|
||||||
|
@TableField(value = "version")
|
||||||
|
@Schema(description = "乐观锁版本")
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
@TableField(value = "\"status\"")
|
||||||
|
@Schema(description = "状态")
|
||||||
|
private Short status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "created_at")
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "updated_at")
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private Date updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
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/10 18:54
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Schema
|
||||||
|
@Data
|
||||||
|
@TableName(value = "keyboard_wallet_transaction")
|
||||||
|
public class KeyboardWalletTransaction {
|
||||||
|
/**
|
||||||
|
* 主键 Id
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@Schema(description="主键 Id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 Id
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
@Schema(description="用户 Id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单 Id
|
||||||
|
*/
|
||||||
|
@TableField(value = "order_id")
|
||||||
|
@Schema(description="订单 Id")
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额
|
||||||
|
*/
|
||||||
|
@TableField(value = "amount")
|
||||||
|
@Schema(description="金额")
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变动类型
|
||||||
|
*/
|
||||||
|
@TableField(value = "\"type\"")
|
||||||
|
@Schema(description="变动类型")
|
||||||
|
private Short type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变动前余额
|
||||||
|
*/
|
||||||
|
@TableField(value = "before_balance")
|
||||||
|
@Schema(description="变动前余额")
|
||||||
|
private BigDecimal beforeBalance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变动后余额
|
||||||
|
*/
|
||||||
|
@TableField(value = "after_balance")
|
||||||
|
@Schema(description="变动后余额")
|
||||||
|
private BigDecimal afterBalance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
@TableField(value = "description")
|
||||||
|
@Schema(description="描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "created_at")
|
||||||
|
@Schema(description="创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.yolo.keyborad.model.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
// ThemeTagItem.java
|
||||||
|
@Data
|
||||||
|
public class ThemeTagItem {
|
||||||
|
private String label;
|
||||||
|
private String color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.yolo.keyborad.model.vo.purchase;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题购买记录响应
|
||||||
|
*/
|
||||||
|
@Schema(description = "主题购买记录响应")
|
||||||
|
@Data
|
||||||
|
public class ThemePurchaseListRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "订单号")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
@Schema(description = "主题ID")
|
||||||
|
private Long themeId;
|
||||||
|
|
||||||
|
@Schema(description = "主题名称")
|
||||||
|
private String themeName;
|
||||||
|
|
||||||
|
@Schema(description = "支付金额")
|
||||||
|
private BigDecimal paidAmount;
|
||||||
|
|
||||||
|
@Schema(description = "支付状态:0待支付 1已支付 2已关闭 3已退款")
|
||||||
|
private Short payStatus;
|
||||||
|
|
||||||
|
@Schema(description = "购买时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "支付时间")
|
||||||
|
private Date paidAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.yolo.keyborad.model.vo.purchase;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题购买响应
|
||||||
|
*/
|
||||||
|
@Schema(description = "主题购买响应")
|
||||||
|
@Data
|
||||||
|
public class ThemePurchaseRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "订单号")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
@Schema(description = "主题ID")
|
||||||
|
private Long themeId;
|
||||||
|
|
||||||
|
@Schema(description = "支付金额")
|
||||||
|
private BigDecimal paidAmount;
|
||||||
|
|
||||||
|
@Schema(description = "剩余余额")
|
||||||
|
private BigDecimal remainingBalance;
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.yolo.keyborad.model.vo.themes;
|
package com.yolo.keyborad.model.vo.themes;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.entity.ThemeTagItem;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @author: ziin
|
* @author: ziin
|
||||||
@@ -34,7 +36,7 @@ public class KeyboardThemesRespVO {
|
|||||||
* 主题标签
|
* 主题标签
|
||||||
*/
|
*/
|
||||||
@Schema(description = "主题标签")
|
@Schema(description = "主题标签")
|
||||||
private String[] themeTag;
|
private List<ThemeTagItem> themeTag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题下载次数
|
* 主题下载次数
|
||||||
@@ -48,6 +50,8 @@ public class KeyboardThemesRespVO {
|
|||||||
@Schema(description = "主题风格")
|
@Schema(description = "主题风格")
|
||||||
private Long themeStyle;
|
private Long themeStyle;
|
||||||
|
|
||||||
|
@Schema(description = "预览图")
|
||||||
|
private String themePreviewImageUrl;
|
||||||
/**
|
/**
|
||||||
* 主题状态
|
* 主题状态
|
||||||
*/
|
*/
|
||||||
@@ -65,4 +69,7 @@ public class KeyboardThemesRespVO {
|
|||||||
|
|
||||||
@Schema(description = "是否免费")
|
@Schema(description = "是否免费")
|
||||||
private Boolean isFree;
|
private Boolean isFree;
|
||||||
|
|
||||||
|
@Schema(description = "当前用户是否已购买")
|
||||||
|
private Boolean isPurchased;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.yolo.keyborad.model.vo.wallet;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10
|
||||||
|
*/
|
||||||
|
@Schema(description = "用户钱包返回对象")
|
||||||
|
@Data
|
||||||
|
public class KeyboardUserWalletRespVO {
|
||||||
|
/**
|
||||||
|
* 余额
|
||||||
|
*/
|
||||||
|
@Schema(description = "余额")
|
||||||
|
private BigDecimal balance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化后的余额显示
|
||||||
|
*/
|
||||||
|
@Schema(description = "格式化后的余额显示")
|
||||||
|
private String balanceDisplay;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.yolo.keyborad.service;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardThemePurchase;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.yolo.keyborad.model.vo.purchase.ThemePurchaseListRespVO;
|
||||||
|
import com.yolo.keyborad.model.vo.purchase.ThemePurchaseRespVO;
|
||||||
|
import com.yolo.keyborad.model.vo.themes.KeyboardThemesRespVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 19:17
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardThemePurchaseService extends IService<KeyboardThemePurchase>{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 购买主题
|
||||||
|
*/
|
||||||
|
ThemePurchaseRespVO purchaseTheme(Long userId, Long themeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户购买记录
|
||||||
|
*/
|
||||||
|
List<ThemePurchaseListRespVO> getUserPurchaseList(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户已购买的主题列表
|
||||||
|
*/
|
||||||
|
List<KeyboardThemesRespVO> getUserPurchasedThemes(Long userId);
|
||||||
|
}
|
||||||
@@ -12,11 +12,28 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface KeyboardThemesService extends IService<KeyboardThemes>{
|
public interface KeyboardThemesService extends IService<KeyboardThemes>{
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按主题风格查询主题列表(未删除且上架)
|
* 按主题风格查询主题列表(未删除且上架),包含用户购买状态
|
||||||
* @param themeStyle 主题风格
|
* @param themeStyle 主题风格
|
||||||
|
* @param userId 用户ID
|
||||||
* @return 主题列表
|
* @return 主题列表
|
||||||
*/
|
*/
|
||||||
List<KeyboardThemesRespVO> selectThemesByStyle(Long themeStyle);
|
List<KeyboardThemesRespVO> selectThemesByStyle(Long themeStyle, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询主题详情
|
||||||
|
* @param themeId 主题ID
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 主题详情
|
||||||
|
*/
|
||||||
|
KeyboardThemesRespVO getThemeDetail(Long themeId, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐主题列表(按真实下载数量降序)
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 推荐主题列表
|
||||||
|
*/
|
||||||
|
List<KeyboardThemesRespVO> getRecommendedThemes(Long userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.yolo.keyborad.service;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserThemes;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/11 13:31
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardUserThemesService extends IService<KeyboardUserThemes>{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除用户主题(逻辑删除)
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param themeIds 主题ID列表
|
||||||
|
*/
|
||||||
|
void batchDeleteUserThemes(Long userId, List<Long> themeIds);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yolo.keyborad.service;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserWallet;
|
||||||
|
import com.yolo.keyborad.model.vo.wallet.KeyboardUserWalletRespVO;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 18:15
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardUserWalletService extends IService<KeyboardUserWallet>{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户钱包余额
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 钱包余额信息
|
||||||
|
*/
|
||||||
|
KeyboardUserWalletRespVO getWalletBalance(Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.yolo.keyborad.service;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardWalletTransaction;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 18:54
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardWalletTransactionService extends IService<KeyboardWalletTransaction>{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建钱包交易记录
|
||||||
|
*/
|
||||||
|
KeyboardWalletTransaction createTransaction(Long userId, Long orderId, BigDecimal amount,
|
||||||
|
Short type, BigDecimal beforeBalance,
|
||||||
|
BigDecimal afterBalance, String description);
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package com.yolo.keyborad.service.impl;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.common.ErrorCode;
|
||||||
|
import com.yolo.keyborad.exception.BusinessException;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardThemes;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserThemes;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserWallet;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardWalletTransaction;
|
||||||
|
import com.yolo.keyborad.model.vo.purchase.ThemePurchaseListRespVO;
|
||||||
|
import com.yolo.keyborad.model.vo.purchase.ThemePurchaseRespVO;
|
||||||
|
import com.yolo.keyborad.model.vo.themes.KeyboardThemesRespVO;
|
||||||
|
import com.yolo.keyborad.service.KeyboardThemesService;
|
||||||
|
import com.yolo.keyborad.service.KeyboardUserThemesService;
|
||||||
|
import com.yolo.keyborad.service.KeyboardUserWalletService;
|
||||||
|
import com.yolo.keyborad.service.KeyboardWalletTransactionService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardThemePurchase;
|
||||||
|
import com.yolo.keyborad.mapper.KeyboardThemePurchaseMapper;
|
||||||
|
import com.yolo.keyborad.service.KeyboardThemePurchaseService;
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 19:17
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class KeyboardThemePurchaseServiceImpl extends ServiceImpl<KeyboardThemePurchaseMapper, KeyboardThemePurchase> implements KeyboardThemePurchaseService{
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardThemesService themesService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardUserWalletService walletService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardWalletTransactionService transactionService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardUserThemesService userThemesService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public ThemePurchaseRespVO purchaseTheme(Long userId, Long themeId) {
|
||||||
|
// 1. 验证主题是否存在且可购买
|
||||||
|
// 从数据库获取主题信息
|
||||||
|
KeyboardThemes theme = themesService.getById(themeId);
|
||||||
|
// 检查主题是否存在或已被删除
|
||||||
|
if (theme == null || theme.getDeleted()) {
|
||||||
|
throw new BusinessException(ErrorCode.THEME_NOT_FOUND);
|
||||||
|
}
|
||||||
|
// 检查主题状态是否可用(上架状态)
|
||||||
|
if (!theme.getThemeStatus()) {
|
||||||
|
throw new BusinessException(ErrorCode.THEME_NOT_AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否已购买
|
||||||
|
// 查询用户是否已经购买过该主题(支付状态为1表示已支付)
|
||||||
|
Long purchaseCount = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemePurchase::getUserId, userId)
|
||||||
|
.eq(KeyboardThemePurchase::getThemeId, themeId)
|
||||||
|
.eq(KeyboardThemePurchase::getPayStatus, (short) 1)
|
||||||
|
.count();
|
||||||
|
// 如果已购买,抛出异常
|
||||||
|
if (purchaseCount > 0) {
|
||||||
|
throw new BusinessException(ErrorCode.THEME_ALREADY_PURCHASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取用户钱包
|
||||||
|
// 查询用户钱包信息
|
||||||
|
KeyboardUserWallet wallet = walletService.lambdaQuery()
|
||||||
|
.eq(KeyboardUserWallet::getUserId, userId)
|
||||||
|
.one();
|
||||||
|
// 如果钱包不存在,抛出余额不足异常
|
||||||
|
if (wallet == null) {
|
||||||
|
throw new BusinessException(ErrorCode.INSUFFICIENT_BALANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查余额是否充足
|
||||||
|
// 获取主题价格
|
||||||
|
BigDecimal themePrice = theme.getThemePrice();
|
||||||
|
// 比较钱包余额和主题价格,余额不足则抛出异常
|
||||||
|
if (wallet.getBalance().compareTo(themePrice) < 0) {
|
||||||
|
throw new BusinessException(ErrorCode.INSUFFICIENT_BALANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 扣减余额(使用乐观锁)
|
||||||
|
// 记录扣款前余额
|
||||||
|
BigDecimal beforeBalance = wallet.getBalance();
|
||||||
|
// 计算扣款后余额
|
||||||
|
BigDecimal afterBalance = beforeBalance.subtract(themePrice);
|
||||||
|
// 更新钱包余额
|
||||||
|
wallet.setBalance(afterBalance);
|
||||||
|
wallet.setUpdatedAt(new Date());
|
||||||
|
// 执行更新操作,乐观锁机制确保并发安全
|
||||||
|
boolean updateSuccess = walletService.updateById(wallet);
|
||||||
|
if (!updateSuccess) {
|
||||||
|
throw new BusinessException(ErrorCode.OPERATION_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 创建购买记录
|
||||||
|
// 生成唯一订单号:ORDER_时间戳_8位UUID
|
||||||
|
String orderNo = "ORDER_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
// 构建购买记录对象
|
||||||
|
KeyboardThemePurchase purchase = new KeyboardThemePurchase();
|
||||||
|
purchase.setOrderNo(orderNo); // 订单号
|
||||||
|
purchase.setUserId(userId); // 用户ID
|
||||||
|
purchase.setThemeId(themeId); // 主题ID
|
||||||
|
purchase.setCostPoints(themePrice); // 消费积分
|
||||||
|
purchase.setPaidPoints(themePrice); // 实付积分
|
||||||
|
purchase.setPayStatus((short) 1); // 支付状态:1-已支付
|
||||||
|
purchase.setCreatedAt(new Date()); // 创建时间
|
||||||
|
purchase.setPaidAt(new Date()); // 支付时间
|
||||||
|
purchase.setUpdatedAt(new Date()); // 更新时间
|
||||||
|
// 保存购买记录到数据库
|
||||||
|
this.save(purchase);
|
||||||
|
|
||||||
|
// 7. 创建钱包交易记录
|
||||||
|
// 调用交易服务创建一条钱包交易记录
|
||||||
|
KeyboardWalletTransaction transaction = transactionService.createTransaction(
|
||||||
|
userId, // 用户ID
|
||||||
|
purchase.getId(), // 关联的购买记录ID
|
||||||
|
themePrice.negate(), // 交易金额(负数表示支出)
|
||||||
|
(short) 1, // 交易类型:1-购买主题
|
||||||
|
beforeBalance, // 交易前余额
|
||||||
|
afterBalance, // 交易后余额
|
||||||
|
"购买主题: " + theme.getThemeName() // 交易备注
|
||||||
|
);
|
||||||
|
|
||||||
|
// 8. 更新购买记录的交易ID
|
||||||
|
// 将交易记录ID关联到购买记录中
|
||||||
|
purchase.setWalletTxId(transaction.getId());
|
||||||
|
this.updateById(purchase);
|
||||||
|
|
||||||
|
// 9. 添加到用户主题表
|
||||||
|
KeyboardUserThemes userTheme = new KeyboardUserThemes();
|
||||||
|
userTheme.setUserId(userId);
|
||||||
|
userTheme.setThemeId(themeId);
|
||||||
|
userTheme.setCreatedAt(new Date());
|
||||||
|
userTheme.setViewDeleted(false);
|
||||||
|
userThemesService.save(userTheme);
|
||||||
|
|
||||||
|
// 10. 构造返回结果
|
||||||
|
// 创建响应对象,封装购买结果信息
|
||||||
|
ThemePurchaseRespVO respVO = new ThemePurchaseRespVO();
|
||||||
|
respVO.setOrderNo(orderNo); // 订单号
|
||||||
|
respVO.setThemeId(themeId); // 主题ID
|
||||||
|
respVO.setPaidAmount(themePrice); // 支付金额
|
||||||
|
respVO.setRemainingBalance(afterBalance); // 剩余余额
|
||||||
|
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的主题购买记录列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 购买记录列表
|
||||||
|
*/
|
||||||
|
public List<ThemePurchaseListRespVO> getUserPurchaseList(Long userId) {
|
||||||
|
// 1. 查询用户的所有购买记录,按创建时间倒序排列
|
||||||
|
List<KeyboardThemePurchase> purchases = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemePurchase::getUserId, userId) // 根据用户ID筛选
|
||||||
|
.orderByDesc(KeyboardThemePurchase::getCreatedAt) // 按创建时间倒序
|
||||||
|
.list();
|
||||||
|
|
||||||
|
// 2. 将购买记录转换为响应VO对象
|
||||||
|
return purchases.stream().map(purchase -> {
|
||||||
|
// 创建响应VO对象
|
||||||
|
ThemePurchaseListRespVO vo = new ThemePurchaseListRespVO();
|
||||||
|
// 设置订单基本信息
|
||||||
|
vo.setOrderNo(purchase.getOrderNo()); // 订单号
|
||||||
|
vo.setThemeId(purchase.getThemeId()); // 主题ID
|
||||||
|
vo.setPaidAmount(purchase.getPaidPoints()); // 支付金额
|
||||||
|
vo.setPayStatus(purchase.getPayStatus()); // 支付状态
|
||||||
|
vo.setCreatedAt(purchase.getCreatedAt()); // 创建时间
|
||||||
|
vo.setPaidAt(purchase.getPaidAt()); // 支付时间
|
||||||
|
|
||||||
|
// 3. 获取主题详情,填充主题名称
|
||||||
|
KeyboardThemes theme = themesService.getById(purchase.getThemeId());
|
||||||
|
if (theme != null) {
|
||||||
|
vo.setThemeName(theme.getThemeName()); // 主题名称
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}).collect(java.util.stream.Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户已购买的主题列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户已购买的主题详情列表
|
||||||
|
*/
|
||||||
|
public List<KeyboardThemesRespVO> getUserPurchasedThemes(Long userId) {
|
||||||
|
// 1. 查询用户所有已支付的主题ID列表
|
||||||
|
List<Long> themeIds = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemePurchase::getUserId, userId) // 根据用户ID筛选
|
||||||
|
.eq(KeyboardThemePurchase::getPayStatus, (short) 1) // 支付状态为1(已支付)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(KeyboardThemePurchase::getThemeId) // 提取主题ID
|
||||||
|
.distinct() // 去重
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
|
// 2. 如果没有购买记录,返回空列表
|
||||||
|
if (themeIds.isEmpty()) {
|
||||||
|
return java.util.Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 根据主题ID列表查询主题详情,并转换为响应VO对象
|
||||||
|
return themesService.lambdaQuery()
|
||||||
|
.in(KeyboardThemes::getId, themeIds) // 根据主题ID列表查询
|
||||||
|
.eq(KeyboardThemes::getDeleted, false) // 排除已删除的主题
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(theme -> {
|
||||||
|
// 创建响应VO对象并填充主题信息
|
||||||
|
KeyboardThemesRespVO vo = new KeyboardThemesRespVO();
|
||||||
|
vo.setId(theme.getId()); // 主题ID
|
||||||
|
vo.setThemePreviewImageUrl(theme.getThemePreviewImageUrl()); // 主题预览图片
|
||||||
|
vo.setThemeName(theme.getThemeName()); // 主题名称
|
||||||
|
vo.setThemePrice(theme.getThemePrice()); // 主题价格
|
||||||
|
vo.setThemeTag(theme.getThemeTag()); // 主题标签
|
||||||
|
vo.setThemeDownload(theme.getThemeDownload()); // 下载地址
|
||||||
|
vo.setThemeStyle(theme.getThemeStyle()); // 主题风格
|
||||||
|
vo.setThemeStatus(theme.getThemeStatus()); // 主题状态
|
||||||
|
vo.setThemePurchasesNumber(theme.getThemePurchasesNumber()); // 购买次数
|
||||||
|
vo.setSort(theme.getSort()); // 排序值
|
||||||
|
vo.setIsFree(theme.getIsFree()); // 是否免费
|
||||||
|
return vo;
|
||||||
|
}).collect(java.util.stream.Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
package com.yolo.keyborad.service.impl;
|
package com.yolo.keyborad.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardThemePurchase;
|
||||||
|
import com.yolo.keyborad.service.KeyboardThemePurchaseService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.yolo.keyborad.mapper.KeyboardThemesMapper;
|
import com.yolo.keyborad.mapper.KeyboardThemesMapper;
|
||||||
import com.yolo.keyborad.model.entity.KeyboardThemes;
|
import com.yolo.keyborad.model.entity.KeyboardThemes;
|
||||||
@@ -17,21 +23,129 @@ import com.yolo.keyborad.service.KeyboardThemesService;
|
|||||||
@Service
|
@Service
|
||||||
public class KeyboardThemesServiceImpl extends ServiceImpl<KeyboardThemesMapper, KeyboardThemes> implements KeyboardThemesService {
|
public class KeyboardThemesServiceImpl extends ServiceImpl<KeyboardThemesMapper, KeyboardThemes> implements KeyboardThemesService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,打破循环依赖
|
||||||
|
private KeyboardThemePurchaseService purchaseService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据风格查询主题列表
|
||||||
|
* <p>查询规则:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>当themeStyle为9999时,查询所有主题并按排序字段升序排列</li>
|
||||||
|
* <li>其他情况下,查询指定风格的主题</li>
|
||||||
|
* <li>查询结果均过滤已删除和未启用的主题</li>
|
||||||
|
* <li>返回的主题列表包含用户的购买状态</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param themeStyle 主题风格ID,9999表示查询所有风格
|
||||||
|
* @param userId 用户ID,用于判断主题购买状态
|
||||||
|
* @return 主题列表,包含主题详情和购买状态
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<KeyboardThemesRespVO> selectThemesByStyle(Long themeStyle) {
|
public List<KeyboardThemesRespVO> selectThemesByStyle(Long themeStyle, Long userId) {
|
||||||
|
// 根据风格参数查询主题列表
|
||||||
|
List<KeyboardThemes> themesList;
|
||||||
if (themeStyle == 9999) {
|
if (themeStyle == 9999) {
|
||||||
List<KeyboardThemes> themesList = this.lambdaQuery()
|
// 查询所有主题,按排序字段升序
|
||||||
|
themesList = this.lambdaQuery()
|
||||||
.eq(KeyboardThemes::getDeleted, false)
|
.eq(KeyboardThemes::getDeleted, false)
|
||||||
.eq(KeyboardThemes::getThemeStatus, true)
|
.eq(KeyboardThemes::getThemeStatus, true)
|
||||||
|
.orderByAsc(KeyboardThemes::getSort)
|
||||||
|
.list();
|
||||||
|
} else {
|
||||||
|
// 查询指定风格的主题
|
||||||
|
themesList = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemes::getDeleted, false)
|
||||||
|
.eq(KeyboardThemes::getThemeStatus, true)
|
||||||
|
.eq(KeyboardThemes::getThemeStyle, themeStyle)
|
||||||
.list();
|
.list();
|
||||||
return BeanUtil.copyToList(themesList, KeyboardThemesRespVO.class);
|
|
||||||
}
|
}
|
||||||
List<KeyboardThemes> themesList = this.lambdaQuery()
|
|
||||||
|
// 查询用户已购买的主题ID集合
|
||||||
|
Set<Long> purchasedThemeIds = purchaseService.lambdaQuery()
|
||||||
|
.eq(KeyboardThemePurchase::getUserId, userId)
|
||||||
|
.eq(KeyboardThemePurchase::getPayStatus, (short) 1)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(KeyboardThemePurchase::getThemeId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 转换为VO并设置购买状态
|
||||||
|
return themesList.stream().map(theme -> {
|
||||||
|
KeyboardThemesRespVO vo = BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class);
|
||||||
|
vo.setIsPurchased(purchasedThemeIds.contains(theme.getId()));
|
||||||
|
return vo;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主题详情
|
||||||
|
* <p>查询指定ID的主题详情,并返回用户对该主题的购买状态</p>
|
||||||
|
*
|
||||||
|
* @param themeId 主题ID
|
||||||
|
* @param userId 用户ID,用于判断购买状态
|
||||||
|
* @return 主题详情VO,包含主题信息和购买状态;如果主题不存在或已删除则返回null
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public KeyboardThemesRespVO getThemeDetail(Long themeId, Long userId) {
|
||||||
|
// 查询主题详情
|
||||||
|
KeyboardThemes theme = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemes::getId, themeId)
|
||||||
.eq(KeyboardThemes::getDeleted, false)
|
.eq(KeyboardThemes::getDeleted, false)
|
||||||
.eq(KeyboardThemes::getThemeStatus, true)
|
.eq(KeyboardThemes::getThemeStatus, true)
|
||||||
.eq(KeyboardThemes::getThemeStyle, themeStyle)
|
.one();
|
||||||
.list();
|
|
||||||
return BeanUtil.copyToList(themesList, KeyboardThemesRespVO.class);
|
// 主题不存在或已删除,返回null
|
||||||
|
if (theme == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户是否购买该主题
|
||||||
|
boolean isPurchased = purchaseService.lambdaQuery()
|
||||||
|
.eq(KeyboardThemePurchase::getUserId, userId)
|
||||||
|
.eq(KeyboardThemePurchase::getThemeId, themeId)
|
||||||
|
.eq(KeyboardThemePurchase::getPayStatus, (short) 1)
|
||||||
|
.exists();
|
||||||
|
|
||||||
|
// 转换为VO并设置购买状态
|
||||||
|
KeyboardThemesRespVO vo = BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class);
|
||||||
|
vo.setIsPurchased(isPurchased);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/*
|
||||||
|
获取推荐主题列表
|
||||||
|
<p>推荐规则:根据真实下载量降序排序,排除用户已购买的主题,最多返回8个主题</p>
|
||||||
|
|
||||||
|
@param userId 用户ID
|
||||||
|
* @return 推荐主题列表,包含主题详情和购买状态(推荐列表中的主题购买状态均为未购买)
|
||||||
|
*/
|
||||||
|
public List<KeyboardThemesRespVO> getRecommendedThemes(Long userId) {
|
||||||
|
// 查询用户已购买的主题ID集合
|
||||||
|
Set<Long> purchasedThemeIds = purchaseService.lambdaQuery()
|
||||||
|
.eq(KeyboardThemePurchase::getUserId, userId)
|
||||||
|
.eq(KeyboardThemePurchase::getPayStatus, (short) 1)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(KeyboardThemePurchase::getThemeId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 查询推荐主题列表:未删除、已启用、未购买、按真实下载量降序、限制8条
|
||||||
|
return this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemes::getDeleted, false)
|
||||||
|
.eq(KeyboardThemes::getThemeStatus, true)
|
||||||
|
.notIn(!purchasedThemeIds.isEmpty(), KeyboardThemes::getId, purchasedThemeIds)
|
||||||
|
.orderByDesc(KeyboardThemes::getRealDownloadCount)
|
||||||
|
.last("LIMIT 8")
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(theme -> {
|
||||||
|
KeyboardThemesRespVO vo = BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class);
|
||||||
|
// 推荐列表中的主题均为未购买状态
|
||||||
|
vo.setIsPurchased(false);
|
||||||
|
return vo;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.yolo.keyborad.service.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserThemes;
|
||||||
|
import com.yolo.keyborad.mapper.KeyboardUserThemesMapper;
|
||||||
|
import com.yolo.keyborad.service.KeyboardUserThemesService;
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/11 13:31
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class KeyboardUserThemesServiceImpl extends ServiceImpl<KeyboardUserThemesMapper, KeyboardUserThemes> implements KeyboardUserThemesService{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除用户主题(逻辑删除)
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param themeIds 主题ID列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void batchDeleteUserThemes(Long userId, List<Long> themeIds) {
|
||||||
|
this.lambdaUpdate()
|
||||||
|
.eq(KeyboardUserThemes::getUserId, userId)
|
||||||
|
.in(KeyboardUserThemes::getThemeId, themeIds)
|
||||||
|
.set(KeyboardUserThemes::getViewDeleted, true)
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.yolo.keyborad.service.impl;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.vo.wallet.KeyboardUserWalletRespVO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yolo.keyborad.mapper.KeyboardUserWalletMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardUserWallet;
|
||||||
|
import com.yolo.keyborad.service.KeyboardUserWalletService;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 18:15
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class KeyboardUserWalletServiceImpl extends ServiceImpl<KeyboardUserWalletMapper, KeyboardUserWallet> implements KeyboardUserWalletService{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyboardUserWalletRespVO getWalletBalance(Long userId) {
|
||||||
|
KeyboardUserWallet wallet = this.lambdaQuery()
|
||||||
|
.eq(KeyboardUserWallet::getUserId, userId)
|
||||||
|
.one();
|
||||||
|
KeyboardUserWalletRespVO respVO = new KeyboardUserWalletRespVO();
|
||||||
|
BigDecimal balance = (wallet == null) ? BigDecimal.ZERO : wallet.getBalance();
|
||||||
|
respVO.setBalance(balance);
|
||||||
|
respVO.setBalanceDisplay(formatBalance(balance));
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatBalance(BigDecimal balance) {
|
||||||
|
if (balance.compareTo(new BigDecimal("10000")) >= 0) {
|
||||||
|
BigDecimal kValue = balance.divide(new BigDecimal("1000"), 2, RoundingMode.HALF_UP);
|
||||||
|
return kValue.stripTrailingZeros().toPlainString() + "K";
|
||||||
|
}
|
||||||
|
return balance.setScale(2, RoundingMode.HALF_UP).toPlainString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.yolo.keyborad.service.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yolo.keyborad.mapper.KeyboardWalletTransactionMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardWalletTransaction;
|
||||||
|
import com.yolo.keyborad.service.KeyboardWalletTransactionService;
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2025/12/10 18:54
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class KeyboardWalletTransactionServiceImpl extends ServiceImpl<KeyboardWalletTransactionMapper, KeyboardWalletTransaction> implements KeyboardWalletTransactionService{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyboardWalletTransaction createTransaction(Long userId, Long orderId, BigDecimal amount,
|
||||||
|
Short type, BigDecimal beforeBalance,
|
||||||
|
BigDecimal afterBalance, String description) {
|
||||||
|
KeyboardWalletTransaction transaction = new KeyboardWalletTransaction();
|
||||||
|
transaction.setUserId(userId);
|
||||||
|
transaction.setOrderId(orderId);
|
||||||
|
transaction.setAmount(amount);
|
||||||
|
transaction.setType(type);
|
||||||
|
transaction.setBeforeBalance(beforeBalance);
|
||||||
|
transaction.setAfterBalance(afterBalance);
|
||||||
|
transaction.setDescription(description);
|
||||||
|
transaction.setCreatedAt(new Date());
|
||||||
|
this.save(transaction);
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// ThemeTagTypeHandler.java
|
||||||
|
package com.yolo.keyborad.typehandler;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.ThemeTagItem;
|
||||||
|
import org.apache.ibatis.type.BaseTypeHandler;
|
||||||
|
import org.apache.ibatis.type.JdbcType;
|
||||||
|
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||||
|
import org.apache.ibatis.type.MappedTypes;
|
||||||
|
import org.postgresql.util.PGobject;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@MappedTypes(List.class)
|
||||||
|
@MappedJdbcTypes(JdbcType.OTHER) // PostgreSQL jsonb = OTHER
|
||||||
|
public class ThemeTagTypeHandler extends BaseTypeHandler<List<ThemeTagItem>> {
|
||||||
|
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonNullParameter(PreparedStatement ps, int i,
|
||||||
|
List<ThemeTagItem> parameter,
|
||||||
|
JdbcType jdbcType) throws SQLException {
|
||||||
|
try {
|
||||||
|
String json = MAPPER.writeValueAsString(parameter);
|
||||||
|
|
||||||
|
PGobject pgObject = new PGobject();
|
||||||
|
pgObject.setType("jsonb");
|
||||||
|
pgObject.setValue(json);
|
||||||
|
|
||||||
|
ps.setObject(i, pgObject);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SQLException("Failed to convert themeTag to jsonb", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThemeTagItem> getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||||
|
String json = rs.getString(columnName);
|
||||||
|
return parseJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThemeTagItem> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||||
|
String json = rs.getString(columnIndex);
|
||||||
|
return parseJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThemeTagItem> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||||
|
String json = cs.getString(columnIndex);
|
||||||
|
return parseJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ThemeTagItem> parseJson(String json) throws SQLException {
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return MAPPER.readValue(json, new TypeReference<List<ThemeTagItem>>() {});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SQLException("Failed to parse jsonb to List<ThemeTagItem>: " + json, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ sa-token:
|
|||||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||||
active-timeout: -1
|
active-timeout: -1
|
||||||
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||||
is-concurrent: false
|
is-concurrent: true
|
||||||
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||||
is-share: false
|
is-share: false
|
||||||
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||||
|
|||||||
27
src/main/resources/mapper/KeyboardThemePurchaseMapper.xml
Normal file
27
src/main/resources/mapper/KeyboardThemePurchaseMapper.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?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.KeyboardThemePurchaseMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardThemePurchase">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table keyboard_theme_purchase-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="order_no" jdbcType="VARCHAR" property="orderNo" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="theme_id" jdbcType="BIGINT" property="themeId" />
|
||||||
|
<result column="cost_points" jdbcType="NUMERIC" property="costPoints" />
|
||||||
|
<result column="paid_points" jdbcType="NUMERIC" property="paidPoints" />
|
||||||
|
<result column="pay_status" jdbcType="SMALLINT" property="payStatus" />
|
||||||
|
<result column="wallet_tx_id" jdbcType="BIGINT" property="walletTxId" />
|
||||||
|
<result column="refund_points" jdbcType="INTEGER" property="refundPoints" />
|
||||||
|
<result column="refunded_at" jdbcType="TIMESTAMP" property="refundedAt" />
|
||||||
|
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||||
|
<result column="paid_at" jdbcType="TIMESTAMP" property="paidAt" />
|
||||||
|
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
|
||||||
|
<result column="remark" jdbcType="VARCHAR" property="remark" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, order_no, user_id, theme_id, cost_points, paid_points, pay_status, wallet_tx_id,
|
||||||
|
refund_points, refunded_at, created_at, paid_at, updated_at, remark
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<id column="id" jdbcType="BIGINT" property="id" />
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
<result column="theme_name" jdbcType="VARCHAR" property="themeName" />
|
<result column="theme_name" jdbcType="VARCHAR" property="themeName" />
|
||||||
<result column="theme_price" jdbcType="NUMERIC" property="themePrice" />
|
<result column="theme_price" jdbcType="NUMERIC" property="themePrice" />
|
||||||
<result column="theme_tag" jdbcType="ARRAY" property="themeTag" typeHandler="com.yolo.keyborad.typehandler.StringArrayTypeHandler" />
|
<result column="theme_tag" jdbcType="VARCHAR" property="themeTag"/>
|
||||||
<result column="theme_download" jdbcType="VARCHAR" property="themeDownload" />
|
<result column="theme_download" jdbcType="VARCHAR" property="themeDownload" />
|
||||||
<result column="theme_style" jdbcType="BIGINT" property="themeStyle" />
|
<result column="theme_style" jdbcType="BIGINT" property="themeStyle" />
|
||||||
<result column="theme_status" jdbcType="BOOLEAN" property="themeStatus" />
|
<result column="theme_status" jdbcType="BOOLEAN" property="themeStatus" />
|
||||||
|
|||||||
18
src/main/resources/mapper/KeyboardUserThemesMapper.xml
Normal file
18
src/main/resources/mapper/KeyboardUserThemesMapper.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?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.KeyboardUserThemesMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardUserThemes">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table keyboard_user_themes-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="theme_id" jdbcType="BIGINT" property="themeId" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||||
|
<result column="view_deleted" jdbcType="BOOLEAN" property="viewDeleted" />
|
||||||
|
<result column="updated_at" jdbcType="BOOLEAN" property="updatedAt" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, theme_id, user_id, created_at, view_deleted, updated_at
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
19
src/main/resources/mapper/KeyboardUserWalletMapper.xml
Normal file
19
src/main/resources/mapper/KeyboardUserWalletMapper.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?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.KeyboardUserWalletMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardUserWallet">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table keyboard_user_wallet-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="balance" jdbcType="NUMERIC" property="balance" />
|
||||||
|
<result column="version" jdbcType="INTEGER" property="version" />
|
||||||
|
<result column="status" jdbcType="SMALLINT" property="status" />
|
||||||
|
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||||
|
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, user_id, balance, version, "status", created_at, updated_at
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?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.KeyboardWalletTransactionMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardWalletTransaction">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table keyboard_wallet_transaction-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="order_id" jdbcType="BIGINT" property="orderId" />
|
||||||
|
<result column="amount" jdbcType="NUMERIC" property="amount" />
|
||||||
|
<result column="type" jdbcType="SMALLINT" property="type" />
|
||||||
|
<result column="before_balance" jdbcType="NUMERIC" property="beforeBalance" />
|
||||||
|
<result column="after_balance" jdbcType="NUMERIC" property="afterBalance" />
|
||||||
|
<result column="description" jdbcType="VARCHAR" property="description" />
|
||||||
|
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, user_id, order_id, amount, "type", before_balance, after_balance, description,
|
||||||
|
created_at
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user