diff --git a/config/application-prod.yml b/config/application-prod.yml index 722b4ea..51de5f5 100644 --- a/config/application-prod.yml +++ b/config/application-prod.yml @@ -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' diff --git a/docker-compose.yml b/docker-compose.yml index 29dfe57..74bbf86 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/qr/alipay/custom.png b/qr/alipay/custom.png new file mode 100644 index 0000000..2ec40e5 Binary files /dev/null and b/qr/alipay/custom.png differ diff --git a/qr/wechat/0.01/1.png b/qr/wechat/0.01/1.png new file mode 100644 index 0000000..2548eab Binary files /dev/null and b/qr/wechat/0.01/1.png differ diff --git a/qr/wechat/0.01/2.png b/qr/wechat/0.01/2.png new file mode 100644 index 0000000..2548eab Binary files /dev/null and b/qr/wechat/0.01/2.png differ diff --git a/qr/wechat/1.00/1.png b/qr/wechat/1.00/1.png new file mode 100644 index 0000000..ee43555 Binary files /dev/null and b/qr/wechat/1.00/1.png differ diff --git a/qr/wechat/1.00/2.png b/qr/wechat/1.00/2.png new file mode 100644 index 0000000..ee43555 Binary files /dev/null and b/qr/wechat/1.00/2.png differ diff --git a/qr/wechat/10.00/1.png b/qr/wechat/10.00/1.png new file mode 100644 index 0000000..19ff78c Binary files /dev/null and b/qr/wechat/10.00/1.png differ diff --git a/qr/wechat/10.00/2.png b/qr/wechat/10.00/2.png new file mode 100644 index 0000000..273e9c2 Binary files /dev/null and b/qr/wechat/10.00/2.png differ diff --git a/qr/wechat/29.00/1.png b/qr/wechat/29.00/1.png new file mode 100644 index 0000000..df7a19f Binary files /dev/null and b/qr/wechat/29.00/1.png differ diff --git a/qr/wechat/29.00/2.png b/qr/wechat/29.00/2.png new file mode 100644 index 0000000..df7a19f Binary files /dev/null and b/qr/wechat/29.00/2.png differ diff --git a/qr/wechat/29.00/28.98/1.png b/qr/wechat/29.00/28.98/1.png new file mode 100644 index 0000000..a30953b Binary files /dev/null and b/qr/wechat/29.00/28.98/1.png differ diff --git a/qr/wechat/29.00/28.98/2.png b/qr/wechat/29.00/28.98/2.png new file mode 100644 index 0000000..a30953b Binary files /dev/null and b/qr/wechat/29.00/28.98/2.png differ diff --git a/qr/wechat/29.00/28.99/1.png b/qr/wechat/29.00/28.99/1.png new file mode 100644 index 0000000..2a0b64a Binary files /dev/null and b/qr/wechat/29.00/28.99/1.png differ diff --git a/qr/wechat/29.00/28.99/2.png b/qr/wechat/29.00/28.99/2.png new file mode 100644 index 0000000..2a0b64a Binary files /dev/null and b/qr/wechat/29.00/28.99/2.png differ diff --git a/qr/wechat/49.00/1.png b/qr/wechat/49.00/1.png new file mode 100644 index 0000000..b0cd681 Binary files /dev/null and b/qr/wechat/49.00/1.png differ diff --git a/qr/wechat/49.00/2.png b/qr/wechat/49.00/2.png new file mode 100644 index 0000000..b0cd681 Binary files /dev/null and b/qr/wechat/49.00/2.png differ diff --git a/qr/wechat/5.00/1.png b/qr/wechat/5.00/1.png new file mode 100644 index 0000000..f6eaaf0 Binary files /dev/null and b/qr/wechat/5.00/1.png differ diff --git a/qr/wechat/5.00/2.png b/qr/wechat/5.00/2.png new file mode 100644 index 0000000..f6eaaf0 Binary files /dev/null and b/qr/wechat/5.00/2.png differ diff --git a/qr/wechat/5.00/4.98/1.png b/qr/wechat/5.00/4.98/1.png new file mode 100644 index 0000000..674a6f7 Binary files /dev/null and b/qr/wechat/5.00/4.98/1.png differ diff --git a/qr/wechat/5.00/4.98/2.png b/qr/wechat/5.00/4.98/2.png new file mode 100644 index 0000000..674a6f7 Binary files /dev/null and b/qr/wechat/5.00/4.98/2.png differ diff --git a/qr/wechat/5.00/4.99/1.png b/qr/wechat/5.00/4.99/1.png new file mode 100644 index 0000000..42572f8 Binary files /dev/null and b/qr/wechat/5.00/4.99/1.png differ diff --git a/qr/wechat/5.00/4.99/2.png b/qr/wechat/5.00/4.99/2.png new file mode 100644 index 0000000..42572f8 Binary files /dev/null and b/qr/wechat/5.00/4.99/2.png differ diff --git a/qr/wechat/5.00/5.01/1.png b/qr/wechat/5.00/5.01/1.png new file mode 100644 index 0000000..bfd2c7c Binary files /dev/null and b/qr/wechat/5.00/5.01/1.png differ diff --git a/qr/wechat/5.00/5.01/2.png b/qr/wechat/5.00/5.01/2.png new file mode 100644 index 0000000..bfd2c7c Binary files /dev/null and b/qr/wechat/5.00/5.01/2.png differ diff --git a/qr/wechat/custom.png b/qr/wechat/custom.png new file mode 100644 index 0000000..e6fca3b Binary files /dev/null and b/qr/wechat/custom.png differ diff --git a/qr/wechat_zs/custom.png b/qr/wechat_zs/custom.png new file mode 100644 index 0000000..529e1b4 Binary files /dev/null and b/qr/wechat_zs/custom.png differ diff --git a/src/main/java/com/wendao/config/PayProConfig.java b/src/main/java/com/wendao/config/PayProConfig.java index 43af5c2..4c92a1a 100644 --- a/src/main/java/com/wendao/config/PayProConfig.java +++ b/src/main/java/com/wendao/config/PayProConfig.java @@ -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 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 getProductConfigsByTypes(List 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)) { diff --git a/src/main/java/com/wendao/entity/Product.java b/src/main/java/com/wendao/entity/Product.java index e96c4c0..22ec1f9 100644 --- a/src/main/java/com/wendao/entity/Product.java +++ b/src/main/java/com/wendao/entity/Product.java @@ -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; diff --git a/src/main/java/com/wendao/mapper/ProductMapper.java b/src/main/java/com/wendao/mapper/ProductMapper.java deleted file mode 100644 index 206557f..0000000 --- a/src/main/java/com/wendao/mapper/ProductMapper.java +++ /dev/null @@ -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 { - -} diff --git a/src/main/java/com/wendao/service/ProductService.java b/src/main/java/com/wendao/service/ProductService.java index 1d2031d..46c2587 100644 --- a/src/main/java/com/wendao/service/ProductService.java +++ b/src/main/java/com/wendao/service/ProductService.java @@ -14,4 +14,11 @@ public interface ProductService { ResponseVO get(Integer productId); ResponseVO> getListByType(GetProductListReq req); + + /** + * 内部使用:获取完整 Product(含 downloadUrl),不包装 ResponseVO。 + * 供 OrderServiceImpl 等后端消费者使用。 + * @return Product or null if not found + */ + Product getProductById(Integer productId); } diff --git a/src/main/java/com/wendao/service/impl/OrderServiceImpl.java b/src/main/java/com/wendao/service/impl/OrderServiceImpl.java index 9d0ff9c..85d8984 100644 --- a/src/main/java/com/wendao/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/wendao/service/impl/OrderServiceImpl.java @@ -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()); diff --git a/src/main/java/com/wendao/service/impl/ProductServiceImpl.java b/src/main/java/com/wendao/service/impl/ProductServiceImpl.java index dcc2100..34470bb 100644 --- a/src/main/java/com/wendao/service/impl/ProductServiceImpl.java +++ b/src/main/java/com/wendao/service/impl/ProductServiceImpl.java @@ -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 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> getListByType(GetProductListReq req) { - QueryWrapper productQueryWrapper = new QueryWrapper<>(); - productQueryWrapper.lambda().in(Product::getType,req.getTypes()); - List products = productMapper.selectList(productQueryWrapper); + List configs = payProConfig.getProductConfigsByTypes(req.getTypes()); + List 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; + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 740973d..b517906 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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: '支付宝支付' diff --git a/src/main/resources/db.sql b/src/main/resources/db.sql index 3359a93..20e5dd6 100644 --- a/src/main/resources/db.sql +++ b/src/main/resources/db.sql @@ -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`; \ No newline at end of file diff --git a/一键启动说明.md b/一键启动说明.md index ed34441..8942256 100644 --- a/一键启动说明.md +++ b/一键启动说明.md @@ -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"] diff --git a/获取支付宝userId.jpg b/获取支付宝userId.jpg new file mode 100644 index 0000000..cf8be70 Binary files /dev/null and b/获取支付宝userId.jpg differ