提交产品维护在配置文件里

This commit is contained in:
codewendao
2026-08-12 15:59:57 +08:00
parent 88dc22e3c8
commit ceccf42849
37 changed files with 139 additions and 66 deletions

View File

@@ -3,6 +3,23 @@
# 请根据实际情况修改下方配置后执行 ./install.sh
# ==========================================
# ---- Spring Mail 邮件发送配置(必改) ----
spring:
mail:
host: smtp.163.com # SMTP 服务器地址
port: 25 # SMTP 端口25/465/587
username: xxxx@163.com # 发件邮箱账号
password: ${MAIL_PASSWORD:your-mail-auth-code} # 邮箱授权码(非登录密码)
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
ssl:
trust: "smtp.163.com"
paypro:
# ---- 必改项 ----
# 站点访问地址改为你的域名或服务器IP
@@ -41,7 +58,7 @@ paypro:
# ---- Token配置 ----
token:
# 加密密钥(务必修改为随机字符串)
value: 123
value: 1
# Token过期天数
expire: 14
@@ -63,6 +80,25 @@ paypro:
# 减额步长(元)可改可不改,改了要对应二维码金额
step: 0.01
# ---- 产品配置 ----
# 所有产品在此定义,修改后重启生效
products:
- id: 1
product-name: "实例产品,演示效果"
money: "5.00"
description: ""
type: "CODE"
extend: ""
download-url: "https://www.baidu.com"
# 更多产品模板:
# - id: 2
# product-name: "游戏礼包"
# money: "10.00"
# description: "包含100金币+10钻石"
# type: "GAME"
# extend: ""
# download-url: ""
# ---- 支付方式(一般不需要调整) ----
payMethods:
- id: 'alipay'

View File

@@ -16,10 +16,11 @@ services:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
test: ["CMD", "mysql", "-uroot", "-proot123", "pay", "-e", "SELECT 1 FROM t_order LIMIT 1"]
interval: 10s
timeout: 5s
retries: 10
retries: 20
start_period: 60s
networks:
- pay-network
@@ -59,7 +60,6 @@ services:
REDIS_PASSWORD: payredis123
REDIS_DB: 1
# 站点 URL
SITE_URL: http://localhost:8889
# 邮箱授权码(可选覆盖)
# MAIL_PASSWORD: your-mail-auth-code
ports:

BIN
qr/alipay/custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
qr/wechat/0.01/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/0.01/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/1.00/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/1.00/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/10.00/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/10.00/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/29.00/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/29.00/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/29.00/28.98/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/29.00/28.98/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/29.00/28.99/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/29.00/28.99/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/49.00/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/49.00/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/4.98/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/4.98/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/4.99/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/4.99/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/5.01/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/5.00/5.01/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
qr/wechat/custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
qr/wechat_zs/custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -6,6 +6,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -194,6 +195,11 @@ public class PayProConfig implements WebMvcConfigurer {
.collect(Collectors.toMap(PayMethod::getId, PayMethod::isUseLocalQrCode));
}
/**
* 产品列表(从 YAML 配置文件读取)
*/
private List<ProductConfig> products;
/**
* 减额匹配配置
*/
@@ -217,6 +223,43 @@ public class PayProConfig implements WebMvcConfigurer {
private BigDecimal step = new BigDecimal("0.01");
}
/**
* 产品配置内部类(对应 YAML 中 paypro.products 列表的每一项)
*/
@Data
public static class ProductConfig {
private Integer id;
private String productName;
private BigDecimal money;
private String description;
private String extend;
private String type;
private String downloadUrl;
}
/**
* 根据 ID 从配置中查找产品
* @return 产品配置,未找到返回 null
*/
public ProductConfig getProductConfigById(Integer id) {
if (products == null) return null;
return products.stream()
.filter(p -> p.getId().equals(id))
.findFirst()
.orElse(null);
}
/**
* 根据类型列表从配置中筛选产品
* @return 匹配的产品列表,无匹配返回空列表
*/
public List<ProductConfig> getProductConfigsByTypes(List<String> types) {
if (products == null || types == null) return Collections.emptyList();
return products.stream()
.filter(p -> types.contains(p.getType()))
.collect(Collectors.toList());
}
@Override
public void addResourceHandlers(org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry registry) {
if (org.springframework.util.StringUtils.hasText(qrDir)) {

View File

@@ -1,25 +1,19 @@
package com.wendao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 产品实体(从 YAML 配置文件读取,非数据库表)
*/
@Data
@TableName("t_product")
public class Product implements Serializable{
public class Product implements Serializable {
/**
* 唯一标识
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String productName;
@@ -31,18 +25,11 @@ public class Product implements Serializable{
*/
private String description;
private Date createTime;
private Date updateTime;
private String extend;
/** 产品类型 GAME CODE*/
/** 产品类型 GAME CODE */
private String type;
@TableLogic(delval = "1",value = "0")
private Integer del;
/** 支付成功后邮件发送的下载链接(仅后端使用,公开接口不会返回) */
private String downloadUrl;

View File

@@ -1,8 +0,0 @@
package com.wendao.mapper;
import com.wendao.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface ProductMapper extends BaseMapper<Product> {
}

View File

@@ -14,4 +14,11 @@ public interface ProductService {
ResponseVO<Product> get(Integer productId);
ResponseVO<List<Product>> getListByType(GetProductListReq req);
/**
* 内部使用:获取完整 Product含 downloadUrl不包装 ResponseVO。
* 供 OrderServiceImpl 等后端消费者使用。
* @return Product or null if not found
*/
Product getProductById(Integer productId);
}

View File

@@ -5,7 +5,7 @@ import com.wendao.config.PayProConfig;
import com.wendao.entity.Order;
import com.wendao.exception.ApiException;
import com.wendao.mapper.OrderMapper;
import com.wendao.mapper.ProductMapper;
import com.wendao.service.ProductService;
import com.wendao.enums.OrderStatesEnum;
import com.wendao.model.req.GetOrderListReq;
import com.wendao.model.req.OpenApiOrderReq;
@@ -63,7 +63,7 @@ public class OrderServiceImpl implements OrderService {
private EmailUtils emailUtils;
@Autowired
ProductMapper productMapper;
ProductService productService;
@Autowired
Snowflake snowflake;
@@ -289,8 +289,8 @@ public class OrderServiceImpl implements OrderService {
//Pay pay = thisService.changePayState(id, 1);
Order pay = orderMapper.selectById(id);
if (pay.getOrderSource().equals("PRODUCT") && pay.getProductId() != null) {
Product product = productMapper.selectById(pay.getProductId());
if (product.getType().equals("CODE")) {
Product product = productService.getProductById(pay.getProductId() != null ? pay.getProductId().intValue() : null);
if (product != null && "CODE".equals(product.getType())) {
emailUtils.sendTemplateMail(payProConfig.getEmail().getSender(), pay.getEmail(), "【Pay个人收款支付系统】支付成功通知附下载链接",
"order-success", pay);
pay.setState(OrderStatesEnum.SUCCESS_PAY.getState());

View File

@@ -1,29 +1,35 @@
package com.wendao.service.impl;
import com.wendao.config.PayProConfig;
import com.wendao.entity.Product;
import com.wendao.model.ResponseVO;
import com.wendao.mapper.ProductMapper;
import com.wendao.model.req.GetProductListReq;
import com.wendao.service.ProductService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description:
**/
* Product 服务实现 —— 从 YAML 配置文件读取产品数据
*/
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
ProductMapper productMapper;
PayProConfig payProConfig;
@Override
public Product getProductById(Integer productId) {
PayProConfig.ProductConfig config = payProConfig.getProductConfigById(productId);
return config != null ? toProductEntity(config) : null;
}
@Override
public ResponseVO<Product> get(Integer productId) {
Product product = productMapper.selectById(productId);
if(product != null){
Product product = getProductById(productId);
if (product != null) {
// 脱敏:公开接口不返回下载链接
product.setDownloadUrl(null);
return ResponseVO.successResponse(product);
@@ -33,11 +39,27 @@ public class ProductServiceImpl implements ProductService {
@Override
public ResponseVO<List<Product>> getListByType(GetProductListReq req) {
QueryWrapper<Product> productQueryWrapper = new QueryWrapper<>();
productQueryWrapper.lambda().in(Product::getType,req.getTypes());
List<Product> products = productMapper.selectList(productQueryWrapper);
List<PayProConfig.ProductConfig> configs = payProConfig.getProductConfigsByTypes(req.getTypes());
List<Product> products = configs.stream()
.map(this::toProductEntity)
.collect(Collectors.toList());
// 脱敏:公开接口不返回下载链接
products.forEach(p -> p.setDownloadUrl(null));
return ResponseVO.successResponse(products);
}
/**
* 将 YAML 配置对象转换为 Product 实体
*/
private Product toProductEntity(PayProConfig.ProductConfig config) {
Product product = new Product();
product.setId(config.getId());
product.setProductName(config.getProductName());
product.setMoney(config.getMoney());
product.setDescription(config.getDescription());
product.setExtend(config.getExtend());
product.setType(config.getType());
product.setDownloadUrl(config.getDownloadUrl());
return product;
}
}

View File

@@ -95,6 +95,15 @@ paypro:
step: 0.01
openapi:
secret: your_openapi_secret_key_here
products:
- id: 1
product-name: "实例产品,演示效果"
money: "5.00"
description: ""
type: "CODE"
extend: ""
download-url: "https://www.baidu.com"
payMethods:
- id: 'alipay'
name: '支付宝支付'

View File

@@ -28,28 +28,9 @@ CREATE TABLE `t_order` (
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for t_product
-- ----------------------------
DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`create_time` datetime NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
`money` decimal(19, 2) NULL DEFAULT NULL COMMENT '金额',
`product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产品名称',
`update_time` datetime NULL DEFAULT NULL,
`item_info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '具体的物品配置JSON格式{gold:1,diamound:1,itemList:[]} 金币 钻石 物品配置',
`type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产品类型GAME CODE',
`extend` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拓展信息',
`del` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
INSERT INTO `t_product` (`id`, `create_time`, `description`, `money`, `product_name`, `update_time`, `item_info`, `type`, `extend`, `del`) VALUES (9, NULL, '项目源码,请关注微信公众号:代码问道获取 ', 1.00, '本项目源码,关注公众号:代码问道 获取', NULL, NULL, 'CODE', NULL, 0);
@@ -74,8 +55,3 @@ CREATE INDEX `idx_actual_amount_state`
-- 备注匹配查询索引(性能优化)
CREATE INDEX `idx_pay_num_create_time`
ON `t_order` (`pay_num`, `create_time`);
ALTER TABLE `t_product`
ADD COLUMN `download_url` VARCHAR(500) NULL DEFAULT NULL
COMMENT '支付成功后邮件发送的下载链接(仅后端使用)'
AFTER `item_info`;

View File

@@ -17,6 +17,7 @@
国内服务器无法直连 Docker Hub需配置镜像源
```bash
sudo systemctl restart docker
sudo tee /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": ["https://mirror.ccs.tencentyun.com"]

BIN
获取支付宝userId.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB