提交产品维护在配置文件里
@@ -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'
|
||||
|
||||
@@ -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
|
After Width: | Height: | Size: 12 KiB |
BIN
qr/wechat/0.01/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/0.01/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/1.00/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/1.00/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/10.00/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/10.00/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/29.00/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/29.00/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/29.00/28.98/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/29.00/28.98/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/29.00/28.99/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/29.00/28.99/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/49.00/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/49.00/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/4.98/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/4.98/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/4.99/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/4.99/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/5.01/1.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/5.00/5.01/2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
qr/wechat/custom.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
qr/wechat_zs/custom.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: '支付宝支付'
|
||||
|
||||
@@ -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`;
|
||||
@@ -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
|
After Width: | Height: | Size: 24 KiB |