From 051969e65f1ef0b77960cf064944f52c84bcc7d4 Mon Sep 17 00:00:00 2001 From: w982 <2802399115@qq.com> Date: Tue, 22 Sep 2026 11:38:41 +0800 Subject: [PATCH 1/6] =?UTF-8?q?docs(core):=20=E5=AE=8C=E5=96=84=20litemall?= =?UTF-8?q?-core=20=E9=80=9A=E7=9F=A5=E4=B8=8E=E4=BB=BB=E5=8A=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=BA=90=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 NotifyType、SmsResult、SmsSender 补充类级 Javadoc 与 @return 标签 - 为 NotifyProperties、NotifyAutoConfiguration 补充类与字段注释(字段对应 litemall.notify 配置项) - 为 AliyunSmsSender、TencentSmsSender 补充类注释并标注阿里云直发桩实现 - 为 QCodeService、Task、TaskService 补充方法 Javadoc - 清理 TaskService 等处的未使用死代码字段;仅涉及注释与文档,无逻辑变更 --- .../litemall/core/notify/AliyunSmsSender.java | 35 ++++++++- .../litemall/core/notify/NotifyService.java | 28 +++++-- .../litemall/core/notify/NotifyType.java | 11 +++ .../litemall/core/notify/SmsResult.java | 8 +- .../litemall/core/notify/SmsSender.java | 11 ++- .../core/notify/TencentSmsSender.java | 26 ++++++- .../config/NotifyAutoConfiguration.java | 27 +++++++ .../core/notify/config/NotifyProperties.java | 37 +++++++++ .../litemall/core/qcode/QCodeService.java | 77 +++++++++++-------- .../linlinjava/litemall/core/task/Task.java | 37 +++++++++ .../litemall/core/task/TaskService.java | 26 ++++++- 11 files changed, 277 insertions(+), 46 deletions(-) diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/AliyunSmsSender.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/AliyunSmsSender.java index 5898e092f..fe06d913d 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/AliyunSmsSender.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/AliyunSmsSender.java @@ -16,17 +16,27 @@ import java.util.HashMap; import java.util.Map; -/* - * 阿里云短信服务 +/** + * 阿里云短信发送器。 + * + *

基于阿里云短信 API(dysmsapi)实现 {@link SmsSender}。 + * 注意:阿里云仅支持模板发送({@link #sendWithTemplate}), + * 直发 {@link #send} 为桩实现,始终返回发送失败;如需直发请走模板发送或改用其他渠道。 */ public class AliyunSmsSender implements SmsSender { + /** 日志 */ private final Log logger = LogFactory.getLog(AliyunSmsSender.class); + /** 阿里云区域 ID,如 cn-hangzhou */ private String regionId; + /** 阿里云 AccessKey ID */ private String accessKeyId; + /** 阿里云 AccessKey Secret */ private String accessKeySecret; + /** 短信签名 */ private String sign; + /** 阿里云短信发送成功的返回码 */ private final String okCode = "OK"; public String getRegionId() { @@ -61,6 +71,16 @@ public void setSign(String sign) { this.sign = sign; } + /** + * 直发短信(桩实现)。 + * + *

阿里云短信 API 不支持不指定模板的直发,此方法为占位实现,始终返回发送失败; + * 实际发送请使用 {@link #sendWithTemplate}。 + * + * @param phone 接收通知的电话号码 + * @param content 短消息内容 + * @return 固定返回 successful=false + */ @Override public SmsResult send(String phone, String content) { SmsResult smsResult = new SmsResult(); @@ -68,6 +88,17 @@ public SmsResult send(String phone, String content) { return smsResult; } + /** + * 通过短信模板发送短信息。 + * + *

阿里云模板参数约定:单参数用 code,多参数用 code/code1/code2...(见下方注释), + * 开发者需保证申请的模板参数名与此约定一致。 + * + * @param phone 接收通知的电话号码 + * @param templateId 通知模板ID + * @param params 模板参数值 + * @return 短信发送结果,失败时 successful=false + */ @Override public SmsResult sendWithTemplate(String phone, String templateId, String[] params) { DefaultProfile profile = DefaultProfile.getProfile(this.regionId, this.accessKeyId, this.accessKeySecret); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java index d47a6335d..369c105cd 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java @@ -9,22 +9,39 @@ import java.util.Map; /** - * 商城通知服务类 + * 商城通知服务类。 + * + *

统一封装邮件、短信两类通知能力,支持直发与模板发送。 + * 通过 {@link #isMailEnable()}、{@link #isSmsEnable()} 判断对应渠道是否已注入启用; + * 邮件与短信的装配在 {@code NotifyAutoConfiguration} 中完成。 */ public class NotifyService { + /** 邮件发送器,为 null 表示未启用邮件通知 */ private MailSender mailSender; + /** 邮件发件人地址(来自配置 spring.mail.sendfrom) */ private String sendFrom; + /** 邮件收件人地址(来自配置 spring.mail.sendto) */ private String sendTo; + /** 短信发送器,为 null 表示未启用短信通知 */ private SmsSender smsSender; + /** 短信模板列表,元素为 {name -> templateId} 的映射 */ private List> smsTemplate = new ArrayList<>(); - private List> wxTemplate = new ArrayList<>(); - + /** + * 邮件通知是否启用 + * + * @return true 表示已注入邮件发送器 + */ public boolean isMailEnable() { return mailSender != null; } + /** + * 短信通知是否启用 + * + * @return true 表示已注入短信发送器 + */ public boolean isSmsEnable() { return smsSender != null; } @@ -70,7 +87,7 @@ public void notifySmsTemplate(String phoneNumber, NotifyType notifyType, String[ * @param phoneNumber 接收通知的电话号码 * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 - * @return + * @return 短信发送结果;若短信未启用或未找到模板则返回 null */ public SmsResult notifySmsTemplateSync(String phoneNumber, NotifyType notifyType, String[] params) { if (smsSender == null) @@ -129,7 +146,4 @@ public void setSmsTemplate(List> smsTemplate) { this.smsTemplate = smsTemplate; } - public void setWxTemplate(List> wxTemplate) { - this.wxTemplate = wxTemplate; - } } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyType.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyType.java index ae599627c..c012510a1 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyType.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyType.java @@ -1,11 +1,22 @@ package org.linlinjava.litemall.core.notify; +/** + * 商城通知类型枚举。 + * + *

每种通知类型对应短信/微信模板配置中的一个名称({@code type}), + * {@link NotifyService} 根据该名称在模板列表中查找对应的模板 ID。 + */ public enum NotifyType { + /** 支付成功通知 */ PAY_SUCCEED("paySucceed"), + /** 发货通知 */ SHIP("ship"), + /** 退款通知 */ REFUND("refund"), + /** 验证码通知(如短信验证码) */ CAPTCHA("captcha"); + /** 模板配置中使用的类型标识 */ private String type; NotifyType(String type) { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java index a35459947..e084b9433 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java @@ -1,16 +1,20 @@ package org.linlinjava.litemall.core.notify; /** - * 发送短信的返回结果 + * 发送短信的返回结果。 + * + *

封装短信是否发送成功,以及短信渠道方返回的原始结果对象。 */ public class SmsResult { + /** 短信是否发送成功(true 表示成功) */ private boolean successful; + /** 短信渠道方返回的原始结果对象 */ private Object result; /** * 短信是否发送成功 * - * @return + * @return true 表示发送成功,false 表示发送失败 */ public boolean isSuccessful() { return successful; diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java index b1ff00bfb..228398bf6 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java @@ -1,5 +1,11 @@ package org.linlinjava.litemall.core.notify; +/** + * 短信发送器接口。 + * + *

定义向指定手机号发送短信(直发或按模板发送)的能力, + * 由具体短信渠道实现,如腾讯云 {@link TencentSmsSender}、阿里云 {@link AliyunSmsSender}。 + */ public interface SmsSender { /** @@ -7,15 +13,18 @@ public interface SmsSender { * * @param phone 接收通知的电话号码 * @param content 短消息内容 + * @return 短信发送结果 */ SmsResult send(String phone, String content); /** * 通过短信模版发送短信息 - * @param phone 接收通知的电话号码 + * + * @param phone 接收通知的电话号码 * @param templateId 通知模板ID * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 + * @return 短信发送结果 */ SmsResult sendWithTemplate(String phone, String templateId, String[] params); } \ No newline at end of file diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java index c4ad29dcc..b1c8914c8 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java @@ -8,15 +8,22 @@ import java.io.IOException; -/* - * 腾讯云短信服务 +/** + * 腾讯云短信发送器。 + * + *

基于腾讯云短信 SDK(SmsSingleSender)实现 {@link SmsSender}, + * 支持直发与按模板发送;发送失败时返回 successful=false 并记录日志。 */ public class TencentSmsSender implements SmsSender { + /** 日志 */ private final Log logger = LogFactory.getLog(TencentSmsSender.class); + /** 腾讯云短信发送器(由配置注入) */ private SmsSingleSender sender; + /** 短信签名 */ private String sign; + //未使用 public SmsSingleSender getSender() { return sender; } @@ -25,6 +32,13 @@ public void setSender(SmsSingleSender sender) { this.sender = sender; } + /** + * 直发短信(不指定模板) + * + * @param phone 接收通知的电话号码 + * @param content 短消息内容 + * @return 短信发送结果,失败时 successful=false + */ @Override public SmsResult send(String phone, String content) { try { @@ -44,6 +58,14 @@ public SmsResult send(String phone, String content) { return smsResult; } + /** + * 通过短信模板发送短信息 + * + * @param phone 接收通知的电话号码 + * @param templateId 通知模板ID + * @param params 模板参数,对应模板中 {1}、{2} 等占位符 + * @return 短信发送结果,失败时 successful=false + */ @Override public SmsResult sendWithTemplate(String phone, String templateId, String[] params) { try { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyAutoConfiguration.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyAutoConfiguration.java index bbf38e186..ff459232d 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyAutoConfiguration.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyAutoConfiguration.java @@ -12,16 +12,28 @@ import java.util.Properties; +/** + * 通知功能自动装配配置。 + * + *

根据 {@code litemall.notify} 配置构建 {@link NotifyService}, + * 邮件/短信启用时分别注入对应发送器;短信渠道按 {@code active} 选择腾讯云或阿里云。 + */ @Configuration @EnableConfigurationProperties(NotifyProperties.class) public class NotifyAutoConfiguration { + /** 通知配置 */ private final NotifyProperties properties; public NotifyAutoConfiguration(NotifyProperties properties) { this.properties = properties; } + /** + * 构建通知服务,按配置装配邮件/短信发送器 + * + * @return 配置完成的 NotifyService + */ @Bean public NotifyService notifyService() { NotifyService notifyService = new NotifyService(); @@ -48,6 +60,11 @@ else if(smsConfig.getActive().equals("aliyun")) { return notifyService; } + /** + * 构建 JavaMail 邮件发送器 + * + * @return 基于配置的 JavaMailSender + */ public JavaMailSender mailSender() { NotifyProperties.Mail mailConfig = properties.getMail(); JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); @@ -68,6 +85,11 @@ public JavaMailSender mailSender() { return mailSender; } + /** + * 构建腾讯云短信发送器 + * + * @return 基于配置的 TencentSmsSender + */ public TencentSmsSender tencentSmsSender() { NotifyProperties.Sms smsConfig = properties.getSms(); TencentSmsSender smsSender = new TencentSmsSender(); @@ -77,6 +99,11 @@ public TencentSmsSender tencentSmsSender() { return smsSender; } + /** + * 构建阿里云短信发送器 + * + * @return 基于配置的 AliyunSmsSender + */ public AliyunSmsSender aliyunSmsSender() { NotifyProperties.Sms smsConfig = properties.getSms(); AliyunSmsSender smsSender = new AliyunSmsSender(); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyProperties.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyProperties.java index e1a3f5925..1170c750c 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyProperties.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/config/NotifyProperties.java @@ -6,9 +6,16 @@ import java.util.List; import java.util.Map; +/** + * 商城通知配置。 + * + *

绑定前缀为 {@code litemall.notify} 的配置项,包含邮件与短信(腾讯云/阿里云)发送相关配置。 + */ @ConfigurationProperties(prefix = "litemall.notify") public class NotifyProperties { + /** 邮件通知配置 */ private Mail mail; + /** 短信通知配置 */ private Sms sms; public Mail getMail() { @@ -27,13 +34,23 @@ public void setSms(Sms sms) { this.sms = sms; } + /** + * 邮件发送配置,对应配置项 litemall.notify.mail.* + */ public static class Mail { + /** 是否启用邮件通知 */ private boolean enable; + /** SMTP 服务器地址 */ private String host; + /** SMTP 登录用户名 */ private String username; + /** SMTP 登录密码 */ private String password; + /** 发件人地址 */ private String sendfrom; + /** 收件人地址 */ private String sendto; + /** SMTP 端口 */ private Integer port; public boolean isEnable() { return enable; @@ -92,12 +109,21 @@ public void setPort(Integer port) { } } + /** + * 短信发送配置,对应配置项 litemall.notify.sms.* + */ public static class Sms { + /** 是否启用短信通知 */ private boolean enable; + /** 当前生效的短信渠道,取值 tencent 或 aliyun */ private String active; + /** 短信签名 */ private String sign; + /** 腾讯云短信配置 */ private Tencent tencent; + /** 阿里云短信配置 */ private Aliyun aliyun; + /** 短信模板列表,元素为 {name -> templateId} 的映射 */ private List> template = new ArrayList<>(); public boolean isEnable() { @@ -148,8 +174,13 @@ public void setAliyun(Aliyun aliyun) { this.aliyun = aliyun; } + /** + * 腾讯云短信配置 + */ public static class Tencent { + /** 腾讯云短信应用 ID */ private int appid; + /** 腾讯云短信应用密钥 */ private String appkey; public int getAppid() { @@ -169,9 +200,15 @@ public void setAppkey(String appkey) { } } + /** + * 阿里云短信配置 + */ public static class Aliyun { + /** 阿里云区域 ID,如 cn-hangzhou */ private String regionId; + /** 阿里云 AccessKey ID */ private String accessKeyId; + /** 阿里云 AccessKey Secret */ private String accessKeySecret; public String getRegionId() { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java index ead254c0d..2e89afec5 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java @@ -18,16 +18,33 @@ import java.io.*; import java.net.URL; +/** + * 小程序二维码 / 分享图生成服务。 + * + *

生成小程序码,并将商品图片、商品名称、商城名称绘制到分享图中, + * 最终保存到存储服务并返回可访问的图片 URL。 + */ @Service public class QCodeService { + /** 日志 */ private final Log logger = LogFactory.getLog(QCodeService.class); + /** 微信小程序服务 */ @Autowired WxMaService wxMaService; + /** 存储服务 */ @Autowired private StorageService storageService; + /** + * 创建拼团分享图 + * + * @param goodName 商品名称 + * @param goodPicUrl 商品图片地址 + * @param groupon 拼团信息,用于生成带 groupon 参数的二维码 + * @return 分享图 URL;生成失败时返回空字符串 + */ public String createGrouponShareImage(String goodName, String goodPicUrl, LitemallGroupon groupon) { try { //创建该商品的二维码 @@ -57,9 +74,10 @@ public String createGrouponShareImage(String goodName, String goodPicUrl, Litema /** * 创建商品分享图 * - * @param goodId - * @param goodPicUrl - * @param goodName + * @param goodId 商品 ID,用于生成带 goods 参数的二维码 + * @param goodPicUrl 商品图片地址 + * @param goodName 商品名称 + * @return 分享图 URL;未开启自动生成或生成失败时返回空字符串 */ public String createGoodShareImage(String goodId, String goodPicUrl, String goodName) { if (!SystemConfig.isAutoCreateShareImage()) @@ -88,6 +106,12 @@ public String createGoodShareImage(String goodId, String goodPicUrl, String good return ""; } + /** + * 生成分享图在存储服务中的对象键名 + * + * @param goodId 商品/拼团 ID + * @return 存储对象键名,如 GOOD_QCODE_123.jpg + */ private String getKeyName(String goodId) { return "GOOD_QCODE_" + goodId + ".jpg"; } @@ -98,8 +122,8 @@ private String getKeyName(String goodId) { * @param qrCodeImg 二维码图片 * @param goodPicUrl 商品图片地址 * @param goodName 商品名称 - * @return - * @throws IOException + * @return 合成的 JPG 图片字节数组 + * @throws IOException 图片读取失败时抛出 */ private byte[] drawPicture(InputStream qrCodeImg, String goodPicUrl, String goodName) throws IOException { //底图 @@ -131,10 +155,6 @@ private byte[] drawPicture(InputStream qrCodeImg, String goodPicUrl, String good //写上商品名称 drawTextInImg(baseImage, goodName, 65, 867); - //写上商城名称 - // drawTextInImgCenter(baseImage, shopName, 98); - - //转jpg BufferedImage result = new BufferedImage(baseImage.getWidth(), baseImage .getHeight(), BufferedImage.TYPE_3BYTE_BGR); @@ -146,27 +166,14 @@ private byte[] drawPicture(InputStream qrCodeImg, String goodPicUrl, String good return bs.toByteArray(); } - private void drawTextInImgCenter(BufferedImage baseImage, String textToWrite, int y) { - Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); - g2D.setColor(new Color(167, 136, 69)); - - String fontName = "Microsoft YaHei"; - - Font f = new Font(fontName, Font.PLAIN, 28); - g2D.setFont(f); - g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 计算文字长度,计算居中的x点坐标 - FontMetrics fm = g2D.getFontMetrics(f); - int textWidth = fm.stringWidth(textToWrite); - int widthX = (baseImage.getWidth() - textWidth) / 2; - // 表示这段文字在图片上的位置(x,y) .第一个是你设置的内容。 - - g2D.drawString(textToWrite, widthX, y); - // 释放对象 - g2D.dispose(); - } - + /** + * 在图片上绘制文本 + * + * @param baseImage 目标画布 + * @param textToWrite 要绘制的文本 + * @param x 文本左上角 x 坐标 + * @param y 文本左上角 y 坐标 + */ private void drawTextInImg(BufferedImage baseImage, String textToWrite, int x, int y) { Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); g2D.setColor(new Color(167, 136, 69)); @@ -179,6 +186,16 @@ private void drawTextInImg(BufferedImage baseImage, String textToWrite, int x, i g2D.dispose(); } + /** + * 将一张图片绘制到目标画布的指定区域 + * + * @param baseImage 目标画布 + * @param imageToWrite 待绘制的源图片 + * @param x 绘制区域左上角 x 坐标 + * @param y 绘制区域左上角 y 坐标 + * @param width 绘制区域宽度 + * @param heigth 绘制区域高度 + */ private void drawImgInImg(BufferedImage baseImage, BufferedImage imageToWrite, int x, int y, int width, int heigth) { Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/task/Task.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/task/Task.java index bdcc5fb13..187e0a768 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/task/Task.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/task/Task.java @@ -6,30 +6,64 @@ import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; +/** + * 延迟任务抽象基类。 + * + *

实现 {@link Delayed} 与 {@link Runnable},子类在 {@link #run()} 中定义到期要执行的具体逻辑; + * 任务以 id 标识,支持在延迟队列中的排序、相等判断与移除。 + */ public abstract class Task implements Delayed, Runnable{ + /** 任务唯一标识 */ private String id = ""; + /** 任务应执行的绝对时间戳(毫秒) */ private long start = 0; + /** + * 构造一个延迟任务 + * + * @param id 任务唯一标识 + * @param delayInMilliseconds 延迟执行的时间(毫秒) + */ public Task(String id, long delayInMilliseconds){ this.id = id; this.start = System.currentTimeMillis() + delayInMilliseconds; } + /** + * 获取任务标识 + * + * @return 任务唯一标识 + */ public String getId() { return id; } + /** + * 距任务执行的剩余延迟时间 + * + * @param unit 时间单位 + * @return 剩余延迟时间,按指定单位换算,可为负 + */ @Override public long getDelay(TimeUnit unit) { long diff = this.start - System.currentTimeMillis(); return unit.convert(diff, TimeUnit.MILLISECONDS); } + /** + * 按计划执行时间比较任务先后 + * + * @param o 待比较的延迟任务 + * @return 负数/零/正数,表示本任务早于/同时/晚于比较对象 + */ @Override public int compareTo(Delayed o) { return Ints.saturatedCast(this.start - ((Task) o).start); } + /** + * 任务相等性按 id 判断 + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -41,6 +75,9 @@ public boolean equals(Object o) { return this.id.equals(t.getId()); } + /** + * 哈希值基于任务 id + */ @Override public int hashCode() { return this.id.hashCode(); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java index 75c50a9fc..6d11b85de 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java @@ -5,14 +5,26 @@ import java.util.concurrent.DelayQueue; import java.util.concurrent.Executors; +/** + * 延迟任务调度服务。 + * + *

内部使用 {@link DelayQueue} 保存待执行的延迟任务,并在启动时启动一个常驻线程 + * 消费队列中的到期任务;对外提供任务的添加与移除能力。 + */ @Component public class TaskService { - private TaskService taskService; + + /** 延迟任务队列,任务按到期时间排序 */ private DelayQueue delayQueue = new DelayQueue(); + /** + * 启动任务消费线程。 + * + *

单线程常驻消费 {@link DelayQueue},任务到期即执行;单个任务异常不会中断消费循环。 + */ @PostConstruct private void init() { - taskService = this; + Executors.newSingleThreadExecutor().execute(new Runnable() { @Override @@ -29,6 +41,11 @@ public void run() { }); } + /** + * 添加延迟任务,已存在(按 id 相等)的任务不重复添加 + * + * @param task 待添加的延迟任务 + */ public void addTask(Task task){ if(delayQueue.contains(task)){ return; @@ -36,6 +53,11 @@ public void addTask(Task task){ delayQueue.add(task); } + /** + * 移除延迟任务 + * + * @param task 待移除的延迟任务 + */ public void removeTask(Task task){ delayQueue.remove(task); } From d068084562e004e0882b74cf63013a6b1b4223f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B6=A6=E5=B0=8F=E6=AC=A3?= <3400278423@qq.com> Date: Tue, 13 Oct 2026 14:46:35 +0800 Subject: [PATCH 2/6] =?UTF-8?q?docs(config):=20=E5=AE=8C=E5=96=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=B1=BB=E4=B8=8E=E7=B3=BB=E7=BB=9F=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E6=9C=8D=E5=8A=A1=E7=9A=84=20Javadoc=20=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../litemall/core/config/AsyncConfig.java | 10 +++++ .../litemall/core/config/CorsConfig.java | 34 ++++++++++++++--- .../core/config/GlobalExceptionHandler.java | 19 ++++++++++ .../litemall/core/config/JacksonConfig.java | 22 +++++++++++ .../core/config/ValidatorConfiguration.java | 19 ++++++++-- .../litemall/core/config/WxConfig.java | 23 ++++++++++-- .../litemall/core/config/WxProperties.java | 17 +++++++++ .../litemall/core/system/SystemConfig.java | 35 ++++++++++++++---- .../core/system/SystemInistService.java | 37 ++++++++++++------- 9 files changed, 183 insertions(+), 33 deletions(-) diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/AsyncConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/AsyncConfig.java index 02534a18e..f1e71362a 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/AsyncConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/AsyncConfig.java @@ -3,6 +3,16 @@ import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; +/** + * 异步任务配置类 + * + *

通过 {@code @EnableAsync} 开启 Spring 对方法级异步调用的支持, + * 使发短信、发邮件、推送模板消息等非核心耗时操作可以从主请求线程中剥离、 + * 在独立线程池中后台执行,避免阻塞小程序接口的响应。

+ * + * @author litemall + * @since 1.0 + */ @Configuration @EnableAsync public class AsyncConfig { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java index 29f01ea25..105b8813d 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java @@ -6,25 +6,47 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; +/** + * 跨域资源共享(CORS)配置类 + * + *

小程序 / H5 前端与后端接口属于不同源(协议、域名、端口任一不同即跨域), + * 浏览器会拦截跨域请求。这里通过注册一个全局 CorsFilter,对 {@code /**} 所有接口 + * 放行跨域预检与实际请求,使前端能够正常调用后端 API。

+ * + *

注意:当前为方便演示,放行源、请求头、请求方法均为 "*",生产环境应收紧为具体域名。

+ * + * @author litemall + * @since 1.0 + */ @Configuration public class CorsConfig { - // 当前跨域请求最大有效时长。这里默认30天 + /** 跨域预检请求(OPTIONS)的缓存时长,单位秒;设为 30 天,避免每次请求都发预检 */ private long maxAge = 30 * 24 * 60 * 60; + /** + * 构造一条 CORS 规则:允许任意来源、任意请求头、任意 HTTP 方法,并携带凭证 + * + * @return 配置好的 CorsConfiguration + */ private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); - corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址 - corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头 - corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法 + corsConfiguration.addAllowedOrigin("*"); // 允许任意访问源(生产环境建议改为具体域名) + corsConfiguration.addAllowedHeader("*"); // 允许请求携带任意请求头 + corsConfiguration.addAllowedMethod("*"); // 允许 GET/POST/PUT/DELETE 等任意方法 corsConfiguration.setMaxAge(maxAge); - corsConfiguration.setAllowCredentials(true); + corsConfiguration.setAllowCredentials(true); // 允许携带 Cookie 等凭证信息 return corsConfiguration; } + /** + * 将跨域规则注册到所有接口路径上,交由 Spring 过滤器链处理 + * + * @return 全局跨域过滤器 + */ @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置 + source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/GlobalExceptionHandler.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/GlobalExceptionHandler.java index a14078ef4..646074eb4 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/GlobalExceptionHandler.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/GlobalExceptionHandler.java @@ -17,12 +17,26 @@ import javax.validation.ValidationException; import java.util.Set; +/** + * 全局异常处理器 + * + *

统一拦截 Controller 层抛出的异常,避免异常堆栈直接暴露给前端。 + * 所有被捕获的异常都会被转换成 {@link ResponseUtil} 约定的统一 JSON 结构 + * (错误码 + 提示消息),同时打印错误日志便于后端排查。

+ * + *

方法命名统一为 badArgument / serious 两类:参数类问题返回“参数错误”, + * 其余未预期异常统一返回“系统错误”。

+ * + * @author litemall + * @since 1.0 + */ @ControllerAdvice @Order public class GlobalExceptionHandler { private Log logger = LogFactory.getLog(GlobalExceptionHandler.class); + /** 非法入参:业务代码主动抛出的 IllegalArgumentException */ @ExceptionHandler(IllegalArgumentException.class) @ResponseBody public Object badArgumentHandler(IllegalArgumentException e) { @@ -30,6 +44,7 @@ public Object badArgumentHandler(IllegalArgumentException e) { return ResponseUtil.badArgumentValue(); } + /** 参数类型不匹配:如预期 Integer 却传入了字符串 */ @ExceptionHandler(MethodArgumentTypeMismatchException.class) @ResponseBody public Object badArgumentHandler(MethodArgumentTypeMismatchException e) { @@ -37,6 +52,7 @@ public Object badArgumentHandler(MethodArgumentTypeMismatchException e) { return ResponseUtil.badArgumentValue(); } + /** 缺少必填的请求参数 */ @ExceptionHandler(MissingServletRequestParameterException.class) @ResponseBody public Object badArgumentHandler(MissingServletRequestParameterException e) { @@ -44,6 +60,7 @@ public Object badArgumentHandler(MissingServletRequestParameterException e) { return ResponseUtil.badArgumentValue(); } + /** 请求体不可读:JSON 格式错误导致无法反序列化 */ @ExceptionHandler(HttpMessageNotReadableException.class) @ResponseBody public Object badArgumentHandler(HttpMessageNotReadableException e) { @@ -51,6 +68,7 @@ public Object badArgumentHandler(HttpMessageNotReadableException e) { return ResponseUtil.badArgumentValue(); } + /** 参数校验异常:提取第一个校验失败字段及其提示信息,返回 402 */ @ExceptionHandler(ValidationException.class) @ResponseBody public Object badArgumentHandler(ValidationException e) { @@ -66,6 +84,7 @@ public Object badArgumentHandler(ValidationException e) { return ResponseUtil.badArgumentValue(); } + /** 兜底处理:所有未被上面匹配的异常,统一按系统错误返回 */ @ExceptionHandler(Exception.class) @ResponseBody public Object seriousHandler(Exception e) { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/JacksonConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/JacksonConfig.java index 74000936d..463bacad9 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/JacksonConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/JacksonConfig.java @@ -20,15 +20,34 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; +/** + * Jackson JSON 序列化全局配置 + * + *

统一规定后端返回给前端的 JSON 规则:

+ * + * + * @author litemall + * @since 1.0 + */ @Configuration public class JacksonConfig { + /** + * 自定义 ObjectMapper 构建器,最高优先级覆盖 Spring Boot 默认配置 + * + * @return Jackson2ObjectMapperBuilderCustomizer 定制器 + */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public Jackson2ObjectMapperBuilderCustomizer customJackson() { return new Jackson2ObjectMapperBuilderCustomizer() { @Override public void customize(Jackson2ObjectMapperBuilder builder) { + // LocalDateTime / LocalDate / LocalTime 的序列化与反序列化统一格式 builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); builder.serializerByType(LocalDate.class, @@ -41,8 +60,11 @@ public void customize(Jackson2ObjectMapperBuilder builder) { new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); + // null 字段不参与序列化 builder.serializationInclusion(JsonInclude.Include.NON_NULL); + // 前端多传未知字段时不报错,提升接口兼容性 builder.failOnUnknownProperties(false); + // 关闭把日期写成时间戳数组的默认行为 builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } }; diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java index 73cc47431..8e6d661e7 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java @@ -8,16 +8,29 @@ import javax.validation.Validator; import javax.validation.ValidatorFactory; +/** + * 参数校验器配置类 + * + *

配置 JSR-303 参数校验(@Valid / @NotBlank 等)使用 Hibernate Validator 实现, + * 并开启 fail_fast:一旦遇到第一个校验失败就立即返回,不必等全部字段校验完, + * 便于接口快速给出明确的错误提示。

+ * + * @author litemall + * @since 1.0 + */ @Configuration public class ValidatorConfiguration { + /** + * 构造并注册全局 Validator + * + * @return fail-fast 模式的 Validator 实例 + */ @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() .addProperty("hibernate.validator.fail_fast", "true") .buildValidatorFactory(); - Validator validator = validatorFactory.getValidator(); - - return validator; + return validatorFactory.getValidator(); } } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxConfig.java index c97a92e53..ef79d3dbb 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxConfig.java @@ -11,11 +11,24 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * 微信小程序与微信支付配置类 + * + *

把 {@link WxProperties}(来自 application.yml 的 litemall.wx.* 配置)装配成 + * 小程序服务 {@link WxMaService} 和微信支付服务 {@link WxPayService} 两个 bean, + * 供登录、解密用户数据、统一下单等业务直接注入使用。

+ * + * @author litemall + * @since 1.0 + */ @Configuration public class WxConfig { @Autowired private WxProperties properties; + /** + * 小程序连接配置:appid、secret、消息校验 token、消息加解密密钥 + */ @Bean public WxMaConfig wxMaConfig() { WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); @@ -26,7 +39,7 @@ public WxMaConfig wxMaConfig() { return config; } - + /** 小程序服务:封装 code 换 session、获取用户信息等能力 */ @Bean public WxMaService wxMaService(WxMaConfig maConfig) { WxMaService service = new WxMaServiceImpl(); @@ -34,6 +47,10 @@ public WxMaService wxMaService(WxMaConfig maConfig) { return service; } + /** + * 微信支付配置:商户号 mchId、商户密钥 mchKey、支付结果通知地址等, + * 交易类型固定 JSAPI(小程序内支付),签名方式 MD5 + */ @Bean public WxPayConfig wxPayConfig() { WxPayConfig payConfig = new WxPayConfig(); @@ -47,11 +64,11 @@ public WxPayConfig wxPayConfig() { return payConfig; } - + /** 微信支付服务:封装统一下单、查询订单、退款等能力 */ @Bean public WxPayService wxPayService(WxPayConfig payConfig) { WxPayService wxPayService = new WxPayServiceImpl(); wxPayService.setConfig(payConfig); return wxPayService; } -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxProperties.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxProperties.java index a5f3dace5..789635662 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxProperties.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/WxProperties.java @@ -3,24 +3,41 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +/** + * 微信小程序 / 微信支付配置属性 + * + *

将 application.yml 中前缀为 {@code litemall.wx} 的配置项绑定到本类字段, + * 供 {@link WxConfig} 装配微信服务时读取。敏感信息(secret、mchKey)应放在配置文件或环境变量中,不要写死在代码里。

+ * + * @author litemall + * @since 1.0 + */ @Configuration @ConfigurationProperties(prefix = "litemall.wx") public class WxProperties { + /** 小程序唯一标识(appid) */ private String appId; + /** 小程序 appSecret,用于 code 换 session_key */ private String appSecret; + /** 微信支付商户号 mch-id */ private String mchId; + /** 微信支付商户密钥 mch-key */ private String mchKey; + /** 支付结果异步通知地址 notify-url */ private String notifyUrl; + /** 商户证书本地路径 key-path */ private String keyPath; + /** 小程序服务器消息校验 token */ private String token; + /** 小程序消息加解密 aes-key */ private String aesKey; public String getToken() { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemConfig.java index 3e1fc0d09..dc9a4038f 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemConfig.java @@ -5,10 +5,17 @@ import java.util.Map; /** - * 系统设置 + * 系统运行配置(内存缓存) + * + *

后台“系统配置”页里的各项参数(首页展示条数、运费规则、订单超时时间、商场名称地址等) + * 都保存在数据库 litemall_system 表中。本类把它们加载到一个静态 Map 里做内存缓存, + * 业务代码通过静态 getter 直接读取,避免每次查库;后台修改配置后调用 updateConfigs 刷新缓存。

+ * + * @author litemall + * @since 1.0 */ public class SystemConfig { - // 小程序相关配置 + // —— 小程序首页展示相关配置 key —— public final static String LITEMALL_WX_INDEX_NEW = "litemall_wx_index_new"; public final static String LITEMALL_WX_INDEX_HOT = "litemall_wx_index_hot"; public final static String LITEMALL_WX_INDEX_BRAND = "litemall_wx_index_brand"; @@ -16,14 +23,14 @@ public class SystemConfig { public final static String LITEMALL_WX_INDEX_CATLOG_LIST = "litemall_wx_catlog_list"; public final static String LITEMALL_WX_INDEX_CATLOG_GOODS = "litemall_wx_catlog_goods"; public final static String LITEMALL_WX_SHARE = "litemall_wx_share"; - // 运费相关配置 + // —— 运费相关配置 key —— public final static String LITEMALL_EXPRESS_FREIGHT_VALUE = "litemall_express_freight_value"; public final static String LITEMALL_EXPRESS_FREIGHT_MIN = "litemall_express_freight_min"; - // 订单相关配置 + // —— 订单相关配置 key(超时时间,单位分钟/天)—— public final static String LITEMALL_ORDER_UNPAID = "litemall_order_unpaid"; public final static String LITEMALL_ORDER_UNCONFIRM = "litemall_order_unconfirm"; public final static String LITEMALL_ORDER_COMMENT = "litemall_order_comment"; - // 商场相关配置 + // —— 商场信息相关配置 key —— public final static String LITEMALL_MALL_NAME = "litemall_mall_name"; public final static String LITEMALL_MALL_ADDRESS = "litemall_mall_address"; public final static String LITEMALL_MALL_PHONE = "litemall_mall_phone"; @@ -31,7 +38,7 @@ public class SystemConfig { public final static String LITEMALL_MALL_LONGITUDE = "litemall_mall_longitude"; public final static String LITEMALL_MALL_Latitude = "litemall_mall_latitude"; - //所有的配置均保存在该 HashMap 中 + /** 全部系统配置的内存缓存,key 为配置名,value 为配置值 */ private static Map SYSTEM_CONFIGS = new HashMap<>(); private static String getConfig(String keyName) { @@ -50,50 +57,62 @@ private static BigDecimal getConfigBigDec(String keyName) { return new BigDecimal(SYSTEM_CONFIGS.get(keyName)); } + /** 首页“新品推荐”展示条数 */ public static Integer getNewLimit() { return getConfigInt(LITEMALL_WX_INDEX_NEW); } + /** 首页“热卖”展示条数 */ public static Integer getHotLimit() { return getConfigInt(LITEMALL_WX_INDEX_HOT); } + /** 首页“品牌”展示条数 */ public static Integer getBrandLimit() { return getConfigInt(LITEMALL_WX_INDEX_BRAND); } + /** 首页“专题”展示条数 */ public static Integer getTopicLimit() { return getConfigInt(LITEMALL_WX_INDEX_TOPIC); } + /** 分类页一级分类展示条数 */ public static Integer getCatlogListLimit() { return getConfigInt(LITEMALL_WX_INDEX_CATLOG_LIST); } + /** 分类下商品展示条数 */ public static Integer getCatlogMoreLimit() { return getConfigInt(LITEMALL_WX_INDEX_CATLOG_GOODS); } + /** 是否自动生成朋友圈分享图 */ public static boolean isAutoCreateShareImage() { return getConfigBoolean(LITEMALL_WX_SHARE); } + /** 运费金额(元) */ public static BigDecimal getFreight() { return getConfigBigDec(LITEMALL_EXPRESS_FREIGHT_VALUE); } + /** 免邮门槛(元) */ public static BigDecimal getFreightLimit() { return getConfigBigDec(LITEMALL_EXPRESS_FREIGHT_MIN); } + /** 未支付订单自动取消时长 */ public static Integer getOrderUnpaid() { return getConfigInt(LITEMALL_ORDER_UNPAID); } + /** 未确认收货自动确认时长 */ public static Integer getOrderUnconfirm() { return getConfigInt(LITEMALL_ORDER_UNCONFIRM); } + /** 订单自动好评时长 */ public static Integer getOrderComment() { return getConfigInt(LITEMALL_ORDER_COMMENT); } @@ -122,13 +141,15 @@ public static String getMallLatitude() { return getConfig(LITEMALL_MALL_Latitude); } + /** 启动时整体载入数据库配置到内存缓存 */ public static void setConfigs(Map configs) { SYSTEM_CONFIGS = configs; } + /** 后台修改配置后,增量更新内存缓存 */ public static void updateConfigs(Map data) { for (Map.Entry entry : data.entrySet()) { SYSTEM_CONFIGS.put(entry.getKey(), entry.getValue()); } } -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java index 37accbef1..ff9238dca 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java @@ -12,16 +12,29 @@ import java.util.Map; /** - * 系统启动服务,用于设置系统配置信息、检查系统状态及打印系统信息 + * 系统启动初始化服务 + * + *

在 Spring 容器启动完成后({@link PostConstruct})自动执行:

+ *
    + *
  1. 从数据库加载全部系统配置;若某配置项缺失则用 DEFAULT_CONFIGS 兜底并回写数据库;
  2. + *
  3. 把配置整体载入 {@link SystemConfig} 的内存缓存;
  4. + *
  5. 在控制台打印一份系统信息横幅(端口、数据库、各模块开关、微信配置等),便于部署后核对。
  6. + *
+ * + *

注:类名 Inist 为原项目历史拼写,未改动以免影响引用。

+ * + * @author litemall + * @since 1.0 */ @Component class SystemInistService { + // 自注入引用,供内部静态/非静态方法间相互调用(保留原项目写法) private SystemInistService systemInistService; - @Autowired private Environment environment; + /** 容器初始化入口:先保存自身引用,再载入配置、打印启动横幅 */ @PostConstruct private void inist() { systemInistService = this; @@ -29,11 +42,11 @@ private void inist() { SystemInfoPrinter.printInfo("Litemall 初始化信息", getSystemInfo()); } - + /** 各项系统配置的默认值:数据库没有时以此为准 */ private final static Map DEFAULT_CONFIGS = new HashMap<>(); static { - // 小程序相关配置默认值 + // 小程序首页各区块默认展示条数 DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_NEW, "6"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_HOT, "6"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_BRAND, "4"); @@ -41,14 +54,14 @@ private void inist() { DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_CATLOG_LIST, "4"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_CATLOG_GOODS, "4"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_SHARE, "false"); - // 运费相关配置默认值 + // 运费:默认 8 元,满 88 元免邮 DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_EXPRESS_FREIGHT_VALUE, "8"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_EXPRESS_FREIGHT_MIN, "88"); - // 订单相关配置默认值 + // 订单:未支付 30 分钟取消,未确认 7 天自动确认,7 天自动好评 DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_UNPAID, "30"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_UNCONFIRM, "7"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_COMMENT, "7"); - // 商城相关配置默认值 + // 商城信息默认值 DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_NAME, "litemall"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_ADDRESS, "上海"); DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_Latitude, "31.201900"); @@ -60,16 +73,16 @@ private void inist() { @Autowired private LitemallSystemConfigService litemallSystemConfigService; + /** 读取数据库配置,缺项用默认值补齐并回写,最后载入内存缓存 */ private void initConfigs() { // 1. 读取数据库全部配置信息 Map configs = litemallSystemConfigService.queryAll(); - // 2. 分析DEFAULT_CONFIGS + // 2. 对默认配置中存在、但数据库缺失的项,补一条默认值并落库 for (Map.Entry entry : DEFAULT_CONFIGS.entrySet()) { if (configs.containsKey(entry.getKey())) { continue; } - configs.put(entry.getKey(), entry.getValue()); litemallSystemConfigService.addConfig(entry.getKey(), entry.getValue()); } @@ -77,18 +90,16 @@ private void initConfigs() { SystemConfig.setConfigs(configs); } + /** 汇总启动时要打印的系统信息(端口、数据源、模块开关、微信配置、系统设置) */ private Map getSystemInfo() { - Map infos = new LinkedHashMap<>(); infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 0, "系统信息"); - // 测试获取application-db.yml配置信息 infos.put("服务器端口", environment.getProperty("server.port")); infos.put("数据库USER", environment.getProperty("spring.datasource.druid.username")); infos.put("数据库地址", environment.getProperty("spring.datasource.druid.url")); infos.put("调试级别", environment.getProperty("logging.level.org.linlinjava.litemall.wx")); - // 测试获取application-core.yml配置信息 infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 1, "模块状态"); infos.put("邮件", environment.getProperty("litemall.notify.mail.enable")); infos.put("短信", environment.getProperty("litemall.notify.sms.enable")); @@ -100,7 +111,6 @@ private Map getSystemInfo() { infos.put("本地对象访问地址", environment.getProperty("litemall.storage.local.address")); infos.put("本地对象访问端口", environment.getProperty("litemall.storage.local.port")); - // 微信相关信息 infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 2, "微信相关"); infos.put("微信APP KEY", environment.getProperty("litemall.wx.app-id")); infos.put("微信APP-SECRET", environment.getProperty("litemall.wx.app-secret")); @@ -108,7 +118,6 @@ private Map getSystemInfo() { infos.put("微信支付MCH-KEY", environment.getProperty("litemall.wx.mch-key")); infos.put("微信支付通知地址", environment.getProperty("litemall.wx.notify-url")); - //测试获取System表配置信息 infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 3, "系统设置"); infos.put("自动创建朋友圈分享图", Boolean.toString(SystemConfig.isAutoCreateShareImage())); infos.put("商场名称", SystemConfig.getMallName()); From 80fd103fea8246aa61697b3eb94d5b733fa2202e Mon Sep 17 00:00:00 2001 From: yonghuwh <2132616158@qq.com> Date: Tue, 13 Oct 2026 15:29:03 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E6=88=90=E5=91=98C(yonghuwh=EF=BC=89?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../litemall/core/util/BeanUtil.java | 22 ++ .../litemall/core/util/CharUtil.java | 19 ++ .../litemall/core/util/DateTimeUtil.java | 6 +- .../litemall/core/util/HttpUtil.java | 23 +- .../linlinjava/litemall/core/util/IpUtil.java | 17 +- .../litemall/core/util/JacksonUtil.java | 103 +++++- .../litemall/core/util/ResponseUtil.java | 87 ++++- .../litemall/core/util/SystemInfoPrinter.java | 41 +++ .../litemall/core/validator/Order.java | 5 + .../core/validator/OrderValidator.java | 10 +- .../litemall/core/validator/Sort.java | 5 + .../core/validator/SortValidator.java | 10 +- litemall-vue/package.json | 1 + .../litemall/wx/service/WxOrderService.java | 47 +++ .../litemall/wx/web/WxOrderController.java | 12 + litemall-wx/config/api.js | 1 + litemall-wx/lib/vant-weapp/icon/index.wxss | 2 +- litemall-wx/pages/checkout/checkout.js | 45 +-- litemall-wx/pages/payResult/payResult.js | 28 +- litemall-wx/project.config.json | 17 +- litemall-wx/project.private.config.json | 302 ++---------------- start.bat | 94 ++++++ stop.bat | 15 + 23 files changed, 542 insertions(+), 370 deletions(-) create mode 100644 start.bat create mode 100644 stop.bat diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/BeanUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/BeanUtil.java index 4f2dceb58..7bdbe3029 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/BeanUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/BeanUtil.java @@ -5,19 +5,41 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +/** + * Spring 容器工具类,用于在非 Spring 管理的类中获取 Bean 实例。 + */ @Component public class BeanUtil implements ApplicationContextAware { + /** 保存 Spring 容器上下文 */ protected static ApplicationContext context; + /** + * 注入 Spring 容器上下文。 + * + * @param applicationContext Spring 容器上下文 + */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } + /** + * 根据 Bean 名称获取 Bean 实例。 + * + * @param name Bean 名称 + * @return Bean 实例 + */ public static Object getBean(String name) { return context.getBean(name); } + /** + * 根据 Bean 类型获取 Bean 实例。 + * + * @param c Bean 类型 + * @param Bean 类型泛型 + * @return Bean 实例 + */ public static T getBean(Class c){ return context.getBean(c); } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/CharUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/CharUtil.java index 628989bc4..84353049f 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/CharUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/CharUtil.java @@ -2,12 +2,23 @@ import java.util.Random; +/** + * 随机字符串工具类。 + */ public class CharUtil { + /** + * 生成指定长度的随机字符串(包含小写字母和数字)。 + * + * @param num 随机字符串的长度 + * @return 随机字符串 + */ public static String getRandomString(Integer num) { + /* 随机字符池:小写字母与数字 */ String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); + /* 循环从字符池中随机取 num 个字符拼接 */ for (int i = 0; i < num; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); @@ -15,10 +26,18 @@ public static String getRandomString(Integer num) { return sb.toString(); } + /** + * 生成指定长度的随机数字字符串。 + * + * @param num 随机字符串的长度 + * @return 随机数字字符串 + */ public static String getRandomNum(Integer num) { + /* 随机字符池:仅数字 */ String base = "0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); + /* 循环从数字池中随机取 num 个字符拼接 */ for (int i = 0; i < num; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/DateTimeUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/DateTimeUtil.java index 99764b978..63da7daec 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/DateTimeUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/DateTimeUtil.java @@ -9,10 +9,10 @@ public class DateTimeUtil { /** - * 格式 yyyy年MM月dd日 HH:mm:ss + * 将日期时间格式化为 "yyyy年MM月dd日 HH:mm:ss" 形式的字符串。 * - * @param dateTime - * @return + * @param dateTime 待格式化的日期时间 + * @return 格式化后的日期时间字符串 */ public static String getDateTimeDisplayString(LocalDateTime dateTime) { DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java index 60713df1e..116bcfcf2 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java @@ -11,11 +11,11 @@ import java.net.URL; import java.util.Map; + /** - * 向指定 URL 发送POST方法的请求 - * - * @return 远程资源的响应结果 + * HTTP 工具类,用于向指定 URL 发送 POST 请求。be'a */ + public class HttpUtil { private static final Log logger = LogFactory.getLog(HttpUtil.class); @@ -27,7 +27,6 @@ public class HttpUtil { * @param params 请求的参数集合 * @return 远程资源的响应结果 */ - @SuppressWarnings("unused") public static String sendPost(String url, Map params) { OutputStreamWriter out = null; BufferedReader in = null; @@ -35,21 +34,21 @@ public static String sendPost(String url, Map params) { try { URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - // 发送POST请求必须设置如下两行 + /*发送POST请求必须设置如下两行*/ conn.setDoOutput(true); conn.setDoInput(true); - // POST方法 + /* POST方法 */ conn.setRequestMethod("POST"); - // 设置通用的请求属性 + /* 设置通用的请求属性 */ conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.connect(); - // 获取URLConnection对象对应的输出流 + /* 获取URLConnection对象对应的输出流 */ out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8"); - // 发送请求参数 + /* 发送请求参数 */ if (params != null) { StringBuilder param = new StringBuilder(); for (Map.Entry entry : params.entrySet()) { @@ -64,9 +63,9 @@ public static String sendPost(String url, Map params) { out.write(param.toString()); } - // flush输出流的缓冲 + /* flush输出流的缓冲 */ out.flush(); - // 定义BufferedReader输入流来读取URL的响应 + /* 定义BufferedReader输入流来读取URL的响应 */ in = new BufferedReader( new InputStreamReader(conn.getInputStream(), "UTF-8")); String line; @@ -76,7 +75,7 @@ public static String sendPost(String url, Map params) { } catch (Exception e) { logger.error(e.getMessage(), e); } - //使用finally块来关闭输出流、输入流 + /* 使用finally块来关闭输出流、输入流 */ finally { try { if (out != null) { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/IpUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/IpUtil.java index f753428e7..46e6ff2ab 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/IpUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/IpUtil.java @@ -14,6 +14,15 @@ public class IpUtil { private static final Log logger = LogFactory.getLog(IpUtil.class); + /** + * 获取客户端真实 IP 地址。 + *

+ * 依次从请求头 x-forwarded-for、Proxy-Client-IP、WL-Proxy-Client-IP 中获取, + * 若都为空则回退到远程地址;经过多级代理时取第一个 IP。 + * + * @param request HTTP 请求 + * @return 客户端 IP 地址 + */ public static String getIpAddr(HttpServletRequest request) { String ipAddress; try { @@ -27,7 +36,7 @@ public static String getIpAddr(HttpServletRequest request) { if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { - // 根据网卡取本机配置的IP + /* 根据网卡取本机配置的IP */ InetAddress inet = null; try { inet = InetAddress.getLocalHost(); @@ -37,9 +46,9 @@ public static String getIpAddr(HttpServletRequest request) { ipAddress = inet.getHostAddress(); } } - // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 - if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() - // = 15 + /* 通过多个代理时,第一个 IP 为客户端真实 IP,多个 IP 以逗号分隔; */ + /* 单个 IP 最长 15 个字符(如 255.255.255.255),超过 15 说明包含多个 IP */ + if (ipAddress != null && ipAddress.length() > 15) { if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java index c64f9ff6d..7fd036f68 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java @@ -11,15 +11,27 @@ import java.util.List; import java.util.Map; +/** + * JSON 解析与序列化工具类,基于 Jackson 实现。 + */ public class JacksonUtil { private static final Log logger = LogFactory.getLog(JacksonUtil.class); + /** + * 解析 JSON 字符串中指定字段的字符串值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的字符串值,不存在或解析失败时返回 null + */ public static String parseString(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); + /* 获取指定字段对应的节点 */ JsonNode leaf = node.get(field); if (leaf != null) return leaf.asText(); @@ -29,15 +41,23 @@ public static String parseString(String body, String field) { return null; } - + /** + * 解析 JSON 字符串中指定字段的字符串列表。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的字符串列表,不存在或解析失败时返回 null + */ public static List parseStringList(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); JsonNode leaf = node.get(field); if (leaf != null) + /* 将字段节点转换为字符串列表 */ return mapper.convertValue(leaf, new TypeReference>() { }); } catch (IOException e) { @@ -46,11 +66,20 @@ public static List parseStringList(String body, String field) { return null; } + /** + * 解析 JSON 字符串中指定字段的整数值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的整数值,不存在或解析失败时返回 null + */ public static Integer parseInteger(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); + /* 获取指定字段对应的节点 */ JsonNode leaf = node.get(field); if (leaf != null) return leaf.asInt(); @@ -60,14 +89,23 @@ public static Integer parseInteger(String body, String field) { return null; } + /** + * 解析 JSON 字符串中指定字段的整数列表。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的整数列表,不存在或解析失败时返回 null + */ public static List parseIntegerList(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); JsonNode leaf = node.get(field); if (leaf != null) + /* 将字段节点转换为整数列表 */ return mapper.convertValue(leaf, new TypeReference>() { }); } catch (IOException e) { @@ -76,12 +114,20 @@ public static List parseIntegerList(String body, String field) { return null; } - + /** + * 解析 JSON 字符串中指定字段的布尔值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的布尔值,不存在或解析失败时返回 null + */ public static Boolean parseBoolean(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); + /* 获取指定字段对应的节点 */ JsonNode leaf = node.get(field); if (leaf != null) return leaf.asBoolean(); @@ -91,13 +137,22 @@ public static Boolean parseBoolean(String body, String field) { return null; } + /** + * 解析 JSON 字符串中指定字段的短整数值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的短整数值,不存在或解析失败时返回 null + */ public static Short parseShort(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); JsonNode leaf = node.get(field); if (leaf != null) { + /* 先按 int 取值再强转为 short */ Integer value = leaf.asInt(); return value.shortValue(); } @@ -107,13 +162,22 @@ public static Short parseShort(String body, String field) { return null; } + /** + * 解析 JSON 字符串中指定字段的字节值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的字节值,不存在或解析失败时返回 null + */ public static Byte parseByte(String body, String field) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); JsonNode leaf = node.get(field); if (leaf != null) { + /* 先按 int 取值再强转为 byte */ Integer value = leaf.asInt(); return value.byteValue(); } @@ -123,12 +187,23 @@ public static Byte parseByte(String body, String field) { return null; } + /** + * 解析 JSON 字符串中指定字段为指定类型的对象。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @param clazz 目标类型 + * @param 目标类型泛型 + * @return 字段对应的对象,不存在或解析失败时返回 null + */ public static T parseObject(String body, String field, Class clazz) { ObjectMapper mapper = new ObjectMapper(); JsonNode node; try { + /* 将 JSON 字符串解析为树形结构 */ node = mapper.readTree(body); node = node.get(field); + /* 将字段节点转换为指定类型的对象 */ return mapper.treeToValue(node, clazz); } catch (IOException e) { logger.error(e.getMessage(), e); @@ -136,13 +211,19 @@ public static T parseObject(String body, String field, Class clazz) { return null; } + /** + * 将 JSON 字符串解析为 JSON 节点。 + * + * @param json JSON 字符串 + * @return JSON 节点,解析失败时返回 null + */ public static Object toNode(String json) { if (json == null) { return null; } ObjectMapper mapper = new ObjectMapper(); try { - + /* 解析 JSON 字符串为 JSON 节点 */ return mapper.readTree(json); } catch (IOException e) { logger.error(e.getMessage(), e); @@ -151,9 +232,16 @@ public static Object toNode(String json) { return null; } + /** + * 将 JSON 字符串解析为 Map。 + * + * @param data JSON 字符串 + * @return Map 对象,解析失败时返回 null + */ public static Map toMap(String data) { ObjectMapper objectMapper = new ObjectMapper(); try { + /* 将 JSON 字符串反序列化为 Map */ return objectMapper.readValue(data, new TypeReference>() { }); } catch (IOException e) { @@ -162,12 +250,19 @@ public static Map toMap(String data) { return null; } + /** + * 将对象序列化为 JSON 字符串。 + * + * @param data 待序列化的对象 + * @return JSON 字符串,序列化失败时返回 null + */ public static String toJson(Object data) { ObjectMapper objectMapper = new ObjectMapper(); try { + /* 将对象序列化为 JSON 字符串 */ return objectMapper.writeValueAsString(data); } catch (JsonProcessingException e) { - e.printStackTrace(); + logger.error(e.getMessage(), e); } return null; } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/ResponseUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/ResponseUtil.java index c7a3521af..db21e87e4 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/ResponseUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/ResponseUtil.java @@ -40,6 +40,11 @@ * */ public class ResponseUtil { + /** + * 构建成功响应。 + * + * @return 成功响应(不含数据) + */ public static Object ok() { Map obj = new HashMap(); obj.put("errno", 0); @@ -47,6 +52,12 @@ public static Object ok() { return obj; } + /** + * 构建成功响应。 + * + * @param data 响应数据 + * @return 成功响应(含数据) + */ public static Object ok(Object data) { Map obj = new HashMap(); obj.put("errno", 0); @@ -55,10 +66,17 @@ public static Object ok(Object data) { return obj; } + /** + * 构建分页列表成功响应。 + * + * @param list 列表数据(Page 类型时自动附带分页信息) + * @return 成功响应(含分页数据) + */ public static Object okList(List list) { Map data = new HashMap(); data.put("list", list); + /* PageHelper 分页对象会附带分页信息,否则按单页处理 */ if (list instanceof Page) { Page page = (Page) list; data.put("total", page.getTotal()); @@ -75,10 +93,18 @@ public static Object okList(List list) { return ok(data); } + /** + * 构建分页列表成功响应。 + * + * @param list 列表数据 + * @param pagedList 用于分页信息的分页对象 + * @return 成功响应(含分页数据) + */ public static Object okList(List list, List pagedList) { Map data = new HashMap(); data.put("list", list); + /* PageHelper 分页对象会附带分页信息,否则按单页处理 */ if (pagedList instanceof Page) { Page page = (Page) pagedList; data.put("total", page.getTotal()); @@ -95,6 +121,11 @@ public static Object okList(List list, List pagedList) { return ok(data); } + /** + * 构建失败响应。 + * + * @return 失败响应(错误码 -1) + */ public static Object fail() { Map obj = new HashMap(); obj.put("errno", -1); @@ -102,6 +133,13 @@ public static Object fail() { return obj; } + /** + * 构建失败响应。 + * + * @param errno 错误码 + * @param errmsg 错误消息 + * @return 失败响应 + */ public static Object fail(int errno, String errmsg) { Map obj = new HashMap(); obj.put("errno", errno); @@ -109,6 +147,14 @@ public static Object fail(int errno, String errmsg) { return obj; } + /** + * 构建带数据的失败响应。 + * + * @param errno 错误码 + * @param errmsg 错误消息 + * @param data 响应数据 + * @return 失败响应(含数据) + */ public static Object fail(int errno, String errmsg, String data) { Map obj = new HashMap(3); obj.put("errno", errno); @@ -117,36 +163,75 @@ public static Object fail(int errno, String errmsg, String data) { return obj; } + /** + * 构建参数错误响应(401)。 + * + * @return 参数错误响应 + */ public static Object badArgument() { return fail(401, "参数不对"); } + /** + * 构建参数值错误响应(402)。 + * + * @return 参数值错误响应 + */ public static Object badArgumentValue() { return fail(402, "参数值不对"); } + /** + * 构建未登录响应(501)。 + * + * @return 未登录响应 + */ public static Object unlogin() { return fail(501, "请登录"); } + /** + * 构建系统内部错误响应(502)。 + * + * @return 系统内部错误响应 + */ public static Object serious() { return fail(502, "系统内部错误"); } + /** + * 构建业务不支持响应(503)。 + * + * @return 业务不支持响应 + */ public static Object unsupport() { return fail(503, "业务不支持"); } + /** + * 构建更新数据失效响应(504)。 + * + * @return 更新数据失效响应 + */ public static Object updatedDateExpired() { return fail(504, "更新数据已经失效"); } + /** + * 构建更新数据失败响应(505)。 + * + * @return 更新数据失败响应 + */ public static Object updatedDataFailed() { return fail(505, "更新数据失败"); } + /** + * 构建无操作权限响应(506)。 + * + * @return 无操作权限响应 + */ public static Object unauthz() { return fail(506, "无操作权限"); } } - diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/SystemInfoPrinter.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/SystemInfoPrinter.java index 51a02f642..b90cba435 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/SystemInfoPrinter.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/SystemInfoPrinter.java @@ -5,16 +5,30 @@ import java.util.Map; +/** + * 系统信息打印工具类。 + *

+ * 用于在应用启动时以统一格式将系统配置信息打印到日志。 + */ public class SystemInfoPrinter { private static final Log logger = LogFactory.getLog(SystemInfoPrinter.class); public static final String CREATE_PART_COPPER = "XOXOXOXOX"; + /** 打印行的最大宽度,用于生成对齐的分隔横线 */ private static int maxSize = 0; + /** + * 打印系统信息。 + * + * @param title 信息标题 + * @param infos 键值对形式的信息集合 + */ public static void printInfo(String title, Map infos) { + /* 先根据内容计算最大行宽 */ setMaxSize(infos); + /* 依次打印头部、各信息行、结尾 */ printHeader(title); for (Map.Entry entry : infos.entrySet()) { @@ -24,6 +38,11 @@ public static void printInfo(String title, Map infos) { printEnd(); } + /** + * 计算打印每行内容的最大宽度。 + * + * @param infos 键值对形式的信息集合 + */ private static void setMaxSize(Map infos) { for (Map.Entry entry : infos.entrySet()) { if (entry.getValue() == null) @@ -35,9 +54,15 @@ private static void setMaxSize(Map infos) { maxSize = size; } + /* 额外留 30 个字符的空隙,保证横线不紧贴内容 */ maxSize = maxSize + 30; } + /** + * 打印信息头部。 + * + * @param title 信息标题 + */ private static void printHeader(String title) { logger.info(getLineCopper()); logger.info(""); @@ -45,11 +70,19 @@ private static void printHeader(String title) { logger.info(""); } + /** + * 打印信息结尾。 + */ private static void printEnd() { logger.info(" "); logger.info(getLineCopper()); } + /** + * 生成一条由等号组成的横线。 + * + * @return 横线字符串 + */ private static String getLineCopper() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < maxSize; i++) { @@ -59,15 +92,23 @@ private static String getLineCopper() { return sb.toString(); } + /** + * 打印一行信息。 + * + * @param head 键 + * @param line 值 + */ private static void printLine(String head, String line) { if (line == null) return; + /* 分区标题(以特殊前缀开头)单独高亮打印 */ if (head.startsWith(CREATE_PART_COPPER)) { logger.info(""); logger.info(" [[ " + line + " ]]"); logger.info(""); } else { + /* 普通键值对:key -> value */ logger.info(" " + head + " -> " + line); } } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Order.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Order.java index dfbaa2b35..4e25178fa 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Order.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Order.java @@ -9,6 +9,11 @@ import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; +/** + * 排序类型校验注解。 + *

+ * 用于校验请求参数中的排序方式是否在允许范围内(默认仅支持 asc 和 desc)。 + */ @Target({METHOD, FIELD, PARAMETER}) @Retention(RUNTIME) @Documented diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java index b163a117b..cbf722f16 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java @@ -6,12 +6,16 @@ import javax.validation.ConstraintValidatorContext; import java.util.List; +/** + * {@link Order} 注解的校验器,校验排序方式是否在允许范围内。 + */ public class OrderValidator implements ConstraintValidator { private List valueList; @Override public void initialize(Order order) { valueList = Lists.newArrayList(); + /* 将允许值统一转为大写,便于校验时忽略大小写 */ for (String val : order.accepts()) { valueList.add(val.toUpperCase()); } @@ -19,9 +23,7 @@ public void initialize(Order order) { @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { - if (!valueList.contains(s.toUpperCase())) { - return false; - } - return true; + /* 校验时忽略大小写 */ + return valueList.contains(s.toUpperCase()); } } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Sort.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Sort.java index 2568e7099..17d8300a0 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Sort.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/Sort.java @@ -9,6 +9,11 @@ import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; +/** + * 排序字段校验注解。 + *

+ * 用于校验请求参数中的排序字段是否在允许范围内(默认仅支持 add_time 和 id)。 + */ @Target({METHOD, FIELD, PARAMETER}) @Retention(RUNTIME) @Documented diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java index eca179bba..2ee14afbc 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java @@ -6,12 +6,16 @@ import javax.validation.ConstraintValidatorContext; import java.util.List; +/** + * {@link Sort} 注解的校验器,校验排序字段是否在允许范围内。 + */ public class SortValidator implements ConstraintValidator { private List valueList; @Override public void initialize(Sort sort) { valueList = Lists.newArrayList(); + /* 将允许值统一转为大写,便于校验时忽略大小写 */ for (String val : sort.accepts()) { valueList.add(val.toUpperCase()); } @@ -19,9 +23,7 @@ public void initialize(Sort sort) { @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { - if (!valueList.contains(s.toUpperCase())) { - return false; - } - return true; + /* 校验时忽略大小写 */ + return valueList.contains(s.toUpperCase()); } } diff --git a/litemall-vue/package.json b/litemall-vue/package.json index e9b816307..5f2bc1b37 100644 --- a/litemall-vue/package.json +++ b/litemall-vue/package.json @@ -19,6 +19,7 @@ "dayjs": "^1.7.7", "js-cookie": "2.2.0", "lodash": "^4.17.21", + "regenerator-runtime": "^0.14.1", "vant": "^2.0.6", "vue": "^2.5.17", "vue-router": "^3.0.1", diff --git a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/WxOrderService.java b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/WxOrderService.java index 4ea16adcc..2f0ead825 100644 --- a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/WxOrderService.java +++ b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/WxOrderService.java @@ -659,6 +659,53 @@ public Object prepay(Integer userId, String body, HttpServletRequest request) { return ResponseUtil.ok(result); } + /** + * 模拟支付(本地开发/演示用,无需真实微信支付资质) + *

+ * 直接标记订单为已支付,跳过微信支付的统一下单和回调流程。 + * + * @param userId 用户ID + * @param body 订单信息,{ orderId:xxx } + * @return 操作结果 + */ + @Transactional + public Object mockPay(Integer userId, String body) { + if (userId == null) { + return ResponseUtil.unlogin(); + } + Integer orderId = JacksonUtil.parseInteger(body, "orderId"); + if (orderId == null) { + return ResponseUtil.badArgument(); + } + + LitemallOrder order = orderService.findById(userId, orderId); + if (order == null) { + return ResponseUtil.badArgumentValue(); + } + if (!order.getUserId().equals(userId)) { + return ResponseUtil.badArgumentValue(); + } + + // 检测是否能够支付 + OrderHandleOption handleOption = OrderUtil.build(order); + if (!handleOption.isPay()) { + return ResponseUtil.fail(ORDER_INVALID_OPERATION, "订单不能支付"); + } + + // 模拟支付成功:直接标记订单为已支付 + order.setOrderStatus(OrderUtil.STATUS_PAY); + order.setPayTime(LocalDateTime.now()); + order.setPayId("MOCK" + order.getOrderSn()); + if (orderService.updateWithOptimisticLocker(order) == 0) { + return ResponseUtil.updatedDateExpired(); + } + + // 取消订单超时未支付任务 + taskService.removeTask(new OrderUnpaidTask(order.getId())); + + return ResponseUtil.ok(); + } + /** * 微信H5支付 * diff --git a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java index 230a259f9..945e0126b 100644 --- a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java +++ b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java @@ -92,6 +92,18 @@ public Object prepay(@LoginUser Integer userId, @RequestBody String body, HttpSe return wxOrderService.prepay(userId, body, request); } + /** + * 模拟支付(本地开发/演示用) + * + * @param userId 用户ID + * @param body 订单信息,{ orderId:xxx } + * @return 操作结果 + */ + @PostMapping("mock-pay") + public Object mockPay(@LoginUser Integer userId, @RequestBody String body) { + return wxOrderService.mockPay(userId, body); + } + /** * 微信H5支付 * @param userId diff --git a/litemall-wx/config/api.js b/litemall-wx/config/api.js index 3d9ebbfc4..d956e15ff 100644 --- a/litemall-wx/config/api.js +++ b/litemall-wx/config/api.js @@ -68,6 +68,7 @@ module.exports = { OrderSubmit: WxApiRoot + 'order/submit', // 提交订单 OrderPrepay: WxApiRoot + 'order/prepay', // 订单的预支付会话 + OrderMockPay: WxApiRoot + 'order/mock-pay', // 模拟支付(本地演示用) OrderList: WxApiRoot + 'order/list', //订单列表 OrderDetail: WxApiRoot + 'order/detail', //订单详情 OrderCancel: WxApiRoot + 'order/cancel', //取消订单 diff --git a/litemall-wx/lib/vant-weapp/icon/index.wxss b/litemall-wx/lib/vant-weapp/icon/index.wxss index 8fa5efcd0..59983ae3d 100644 --- a/litemall-wx/lib/vant-weapp/icon/index.wxss +++ b/litemall-wx/lib/vant-weapp/icon/index.wxss @@ -1 +1 @@ -@import '../common/index.wxss';@font-face{font-weight:400;font-family:vant-icon;font-style:normal;font-display:auto;src:url(https://img.yzcdn.cn/vant/vant-icon-0bc654.woff2) format("woff2"),url(https://img.yzcdn.cn/vant/vant-icon-0bc654.woff) format("woff"),url(https://img.yzcdn.cn/vant/vant-icon-0bc654.ttf) format("truetype")}.van-icon{position:relative;font:normal normal normal 14px/1 vant-icon;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased}.van-icon,.van-icon:before{display:inline-block}.van-icon-add-o:before{content:"\F000"}.van-icon-add-square:before{content:"\F001"}.van-icon-add:before{content:"\F002"}.van-icon-after-sale:before{content:"\F003"}.van-icon-aim:before{content:"\F004"}.van-icon-alipay:before{content:"\F005"}.van-icon-apps-o:before{content:"\F006"}.van-icon-arrow-down:before{content:"\F007"}.van-icon-arrow-left:before{content:"\F008"}.van-icon-arrow-up:before{content:"\F009"}.van-icon-arrow:before{content:"\F00A"}.van-icon-ascending:before{content:"\F00B"}.van-icon-audio:before{content:"\F00C"}.van-icon-award-o:before{content:"\F00D"}.van-icon-award:before{content:"\F00E"}.van-icon-bag-o:before{content:"\F00F"}.van-icon-bag:before{content:"\F010"}.van-icon-balance-list-o:before{content:"\F011"}.van-icon-balance-list:before{content:"\F012"}.van-icon-balance-o:before{content:"\F013"}.van-icon-balance-pay:before{content:"\F014"}.van-icon-bar-chart-o:before{content:"\F015"}.van-icon-bars:before{content:"\F016"}.van-icon-bell:before{content:"\F017"}.van-icon-bill-o:before{content:"\F018"}.van-icon-bill:before{content:"\F019"}.van-icon-birthday-cake-o:before{content:"\F01A"}.van-icon-bookmark-o:before{content:"\F01B"}.van-icon-bookmark:before{content:"\F01C"}.van-icon-browsing-history-o:before{content:"\F01D"}.van-icon-browsing-history:before{content:"\F01E"}.van-icon-brush-o:before{content:"\F01F"}.van-icon-bulb-o:before{content:"\F020"}.van-icon-bullhorn-o:before{content:"\F021"}.van-icon-calender-o:before{content:"\F022"}.van-icon-card:before{content:"\F023"}.van-icon-cart-circle-o:before{content:"\F024"}.van-icon-cart-circle:before{content:"\F025"}.van-icon-cart-o:before{content:"\F026"}.van-icon-cart:before{content:"\F027"}.van-icon-cash-back-record:before{content:"\F028"}.van-icon-cash-on-deliver:before{content:"\F029"}.van-icon-cashier-o:before{content:"\F02A"}.van-icon-certificate:before{content:"\F02B"}.van-icon-chart-trending-o:before{content:"\F02C"}.van-icon-chat-o:before{content:"\F02D"}.van-icon-chat:before{content:"\F02E"}.van-icon-checked:before{content:"\F02F"}.van-icon-circle:before{content:"\F030"}.van-icon-clear:before{content:"\F031"}.van-icon-clock-o:before{content:"\F032"}.van-icon-clock:before{content:"\F033"}.van-icon-close:before{content:"\F034"}.van-icon-closed-eye:before{content:"\F035"}.van-icon-cluster-o:before{content:"\F036"}.van-icon-cluster:before{content:"\F037"}.van-icon-column:before{content:"\F038"}.van-icon-comment-circle-o:before{content:"\F039"}.van-icon-comment-circle:before{content:"\F03A"}.van-icon-comment-o:before{content:"\F03B"}.van-icon-comment:before{content:"\F03C"}.van-icon-completed:before{content:"\F03D"}.van-icon-contact:before{content:"\F03E"}.van-icon-coupon-o:before{content:"\F03F"}.van-icon-coupon:before{content:"\F040"}.van-icon-credit-pay:before{content:"\F041"}.van-icon-cross:before{content:"\F042"}.van-icon-debit-pay:before{content:"\F043"}.van-icon-delete:before{content:"\F044"}.van-icon-descending:before{content:"\F045"}.van-icon-description:before{content:"\F046"}.van-icon-desktop-o:before{content:"\F047"}.van-icon-diamond-o:before{content:"\F048"}.van-icon-diamond:before{content:"\F049"}.van-icon-discount:before{content:"\F04A"}.van-icon-down:before{content:"\F04B"}.van-icon-ecard-pay:before{content:"\F04C"}.van-icon-edit:before{content:"\F04D"}.van-icon-ellipsis:before{content:"\F04E"}.van-icon-empty:before{content:"\F04F"}.van-icon-envelop-o:before{content:"\F050"}.van-icon-exchange:before{content:"\F051"}.van-icon-expand-o:before{content:"\F052"}.van-icon-expand:before{content:"\F053"}.van-icon-eye-o:before{content:"\F054"}.van-icon-eye:before{content:"\F055"}.van-icon-fail:before{content:"\F056"}.van-icon-failure:before{content:"\F057"}.van-icon-filter-o:before{content:"\F058"}.van-icon-fire-o:before{content:"\F059"}.van-icon-fire:before{content:"\F05A"}.van-icon-flag-o:before{content:"\F05B"}.van-icon-flower-o:before{content:"\F05C"}.van-icon-free-postage:before{content:"\F05D"}.van-icon-friends-o:before{content:"\F05E"}.van-icon-friends:before{content:"\F05F"}.van-icon-gem-o:before{content:"\F060"}.van-icon-gem:before{content:"\F061"}.van-icon-gift-card-o:before{content:"\F062"}.van-icon-gift-card:before{content:"\F063"}.van-icon-gift-o:before{content:"\F064"}.van-icon-gift:before{content:"\F065"}.van-icon-gold-coin-o:before{content:"\F066"}.van-icon-gold-coin:before{content:"\F067"}.van-icon-good-job-o:before{content:"\F068"}.van-icon-good-job:before{content:"\F069"}.van-icon-goods-collect-o:before{content:"\F06A"}.van-icon-goods-collect:before{content:"\F06B"}.van-icon-graphic:before{content:"\F06C"}.van-icon-home-o:before{content:"\F06D"}.van-icon-hot-o:before{content:"\F06E"}.van-icon-hot-sale-o:before{content:"\F06F"}.van-icon-hot-sale:before{content:"\F070"}.van-icon-hot:before{content:"\F071"}.van-icon-hotel-o:before{content:"\F072"}.van-icon-idcard:before{content:"\F073"}.van-icon-info-o:before{content:"\F074"}.van-icon-info:before{content:"\F075"}.van-icon-invition:before{content:"\F076"}.van-icon-label-o:before{content:"\F077"}.van-icon-label:before{content:"\F078"}.van-icon-like-o:before{content:"\F079"}.van-icon-like:before{content:"\F07A"}.van-icon-live:before{content:"\F07B"}.van-icon-location-o:before{content:"\F07C"}.van-icon-location:before{content:"\F07D"}.van-icon-lock:before{content:"\F07E"}.van-icon-logistics:before{content:"\F07F"}.van-icon-manager-o:before{content:"\F080"}.van-icon-manager:before{content:"\F081"}.van-icon-map-marked:before{content:"\F082"}.van-icon-medel-o:before{content:"\F083"}.van-icon-medel:before{content:"\F084"}.van-icon-more-o:before{content:"\F085"}.van-icon-more:before{content:"\F086"}.van-icon-music-o:before{content:"\F087"}.van-icon-music:before{content:"\F088"}.van-icon-new-arrival-o:before{content:"\F089"}.van-icon-new-arrival:before{content:"\F08A"}.van-icon-new-o:before{content:"\F08B"}.van-icon-new:before{content:"\F08C"}.van-icon-newspaper-o:before{content:"\F08D"}.van-icon-notes-o:before{content:"\F08E"}.van-icon-orders-o:before{content:"\F08F"}.van-icon-other-pay:before{content:"\F090"}.van-icon-paid:before{content:"\F091"}.van-icon-passed:before{content:"\F092"}.van-icon-pause-circle-o:before{content:"\F093"}.van-icon-pause-circle:before{content:"\F094"}.van-icon-pause:before{content:"\F095"}.van-icon-peer-pay:before{content:"\F096"}.van-icon-pending-payment:before{content:"\F097"}.van-icon-phone-circle-o:before{content:"\F098"}.van-icon-phone-circle:before{content:"\F099"}.van-icon-phone-o:before{content:"\F09A"}.van-icon-phone:before{content:"\F09B"}.van-icon-photo-o:before{content:"\F09C"}.van-icon-photo:before{content:"\F09D"}.van-icon-photograph:before{content:"\F09E"}.van-icon-play-circle-o:before{content:"\F09F"}.van-icon-play-circle:before{content:"\F0A0"}.van-icon-play:before{content:"\F0A1"}.van-icon-plus:before{content:"\F0A2"}.van-icon-point-gift-o:before{content:"\F0A3"}.van-icon-point-gift:before{content:"\F0A4"}.van-icon-points:before{content:"\F0A5"}.van-icon-printer:before{content:"\F0A6"}.van-icon-qr-invalid:before{content:"\F0A7"}.van-icon-qr:before{content:"\F0A8"}.van-icon-question-o:before{content:"\F0A9"}.van-icon-question:before{content:"\F0AA"}.van-icon-records:before{content:"\F0AB"}.van-icon-refund-o:before{content:"\F0AC"}.van-icon-replay:before{content:"\F0AD"}.van-icon-scan:before{content:"\F0AE"}.van-icon-search:before{content:"\F0AF"}.van-icon-send-gift-o:before{content:"\F0B0"}.van-icon-send-gift:before{content:"\F0B1"}.van-icon-service-o:before{content:"\F0B2"}.van-icon-service:before{content:"\F0B3"}.van-icon-setting-o:before{content:"\F0B4"}.van-icon-setting:before{content:"\F0B5"}.van-icon-share:before{content:"\F0B6"}.van-icon-shop-collect-o:before{content:"\F0B7"}.van-icon-shop-collect:before{content:"\F0B8"}.van-icon-shop-o:before{content:"\F0B9"}.van-icon-shop:before{content:"\F0BA"}.van-icon-shopping-cart-o:before{content:"\F0BB"}.van-icon-shopping-cart:before{content:"\F0BC"}.van-icon-shrink:before{content:"\F0BD"}.van-icon-sign:before{content:"\F0BE"}.van-icon-smile-comment-o:before{content:"\F0BF"}.van-icon-smile-comment:before{content:"\F0C0"}.van-icon-smile-o:before{content:"\F0C1"}.van-icon-smile:before{content:"\F0C2"}.van-icon-star-o:before{content:"\F0C3"}.van-icon-star:before{content:"\F0C4"}.van-icon-stop-circle-o:before{content:"\F0C5"}.van-icon-stop-circle:before{content:"\F0C6"}.van-icon-stop:before{content:"\F0C7"}.van-icon-success:before{content:"\F0C8"}.van-icon-thumb-circle-o:before{content:"\F0C9"}.van-icon-thumb-circle:before{content:"\F0CA"}.van-icon-todo-list-o:before{content:"\F0CB"}.van-icon-todo-list:before{content:"\F0CC"}.van-icon-tosend:before{content:"\F0CD"}.van-icon-tv-o:before{content:"\F0CE"}.van-icon-umbrella-circle:before{content:"\F0CF"}.van-icon-underway-o:before{content:"\F0D0"}.van-icon-underway:before{content:"\F0D1"}.van-icon-upgrade:before{content:"\F0D2"}.van-icon-user-circle-o:before{content:"\F0D3"}.van-icon-user-o:before{content:"\F0D4"}.van-icon-video-o:before{content:"\F0D5"}.van-icon-video:before{content:"\F0D6"}.van-icon-vip-card-o:before{content:"\F0D7"}.van-icon-vip-card:before{content:"\F0D8"}.van-icon-volume-o:before{content:"\F0D9"}.van-icon-volume:before{content:"\F0DA"}.van-icon-wap-home-o:before{content:"\F0DB"}.van-icon-wap-home:before{content:"\F0DC"}.van-icon-wap-nav:before{content:"\F0DD"}.van-icon-warn-o:before{content:"\F0DE"}.van-icon-warning-o:before{content:"\F0DF"}.van-icon-warning:before{content:"\F0E0"}.van-icon-weapp-nav:before{content:"\F0E1"}.van-icon-wechat:before{content:"\F0E2"}.van-icon-youzan-shield:before{content:"\F0E3"}:host{display:-webkit-inline-flex;display:inline-flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}.van-icon--image{width:1em;height:1em}.van-icon__image{width:100%;height:100%}.van-icon__info{z-index:1} \ No newline at end of file +@import '../common/index.wxss';@font-face{font-weight:400;font-family:vant-icon;font-style:normal;font-display:auto;src:url(data:font/woff2;base64,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) format("woff2")}.van-icon{position:relative;font:normal normal normal 14px/1 vant-icon;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased}.van-icon,.van-icon:before{display:inline-block}.van-icon-add-o:before{content:"\F000"}.van-icon-add-square:before{content:"\F001"}.van-icon-add:before{content:"\F002"}.van-icon-after-sale:before{content:"\F003"}.van-icon-aim:before{content:"\F004"}.van-icon-alipay:before{content:"\F005"}.van-icon-apps-o:before{content:"\F006"}.van-icon-arrow-down:before{content:"\F007"}.van-icon-arrow-left:before{content:"\F008"}.van-icon-arrow-up:before{content:"\F009"}.van-icon-arrow:before{content:"\F00A"}.van-icon-ascending:before{content:"\F00B"}.van-icon-audio:before{content:"\F00C"}.van-icon-award-o:before{content:"\F00D"}.van-icon-award:before{content:"\F00E"}.van-icon-bag-o:before{content:"\F00F"}.van-icon-bag:before{content:"\F010"}.van-icon-balance-list-o:before{content:"\F011"}.van-icon-balance-list:before{content:"\F012"}.van-icon-balance-o:before{content:"\F013"}.van-icon-balance-pay:before{content:"\F014"}.van-icon-bar-chart-o:before{content:"\F015"}.van-icon-bars:before{content:"\F016"}.van-icon-bell:before{content:"\F017"}.van-icon-bill-o:before{content:"\F018"}.van-icon-bill:before{content:"\F019"}.van-icon-birthday-cake-o:before{content:"\F01A"}.van-icon-bookmark-o:before{content:"\F01B"}.van-icon-bookmark:before{content:"\F01C"}.van-icon-browsing-history-o:before{content:"\F01D"}.van-icon-browsing-history:before{content:"\F01E"}.van-icon-brush-o:before{content:"\F01F"}.van-icon-bulb-o:before{content:"\F020"}.van-icon-bullhorn-o:before{content:"\F021"}.van-icon-calender-o:before{content:"\F022"}.van-icon-card:before{content:"\F023"}.van-icon-cart-circle-o:before{content:"\F024"}.van-icon-cart-circle:before{content:"\F025"}.van-icon-cart-o:before{content:"\F026"}.van-icon-cart:before{content:"\F027"}.van-icon-cash-back-record:before{content:"\F028"}.van-icon-cash-on-deliver:before{content:"\F029"}.van-icon-cashier-o:before{content:"\F02A"}.van-icon-certificate:before{content:"\F02B"}.van-icon-chart-trending-o:before{content:"\F02C"}.van-icon-chat-o:before{content:"\F02D"}.van-icon-chat:before{content:"\F02E"}.van-icon-checked:before{content:"\F02F"}.van-icon-circle:before{content:"\F030"}.van-icon-clear:before{content:"\F031"}.van-icon-clock-o:before{content:"\F032"}.van-icon-clock:before{content:"\F033"}.van-icon-close:before{content:"\F034"}.van-icon-closed-eye:before{content:"\F035"}.van-icon-cluster-o:before{content:"\F036"}.van-icon-cluster:before{content:"\F037"}.van-icon-column:before{content:"\F038"}.van-icon-comment-circle-o:before{content:"\F039"}.van-icon-comment-circle:before{content:"\F03A"}.van-icon-comment-o:before{content:"\F03B"}.van-icon-comment:before{content:"\F03C"}.van-icon-completed:before{content:"\F03D"}.van-icon-contact:before{content:"\F03E"}.van-icon-coupon-o:before{content:"\F03F"}.van-icon-coupon:before{content:"\F040"}.van-icon-credit-pay:before{content:"\F041"}.van-icon-cross:before{content:"\F042"}.van-icon-debit-pay:before{content:"\F043"}.van-icon-delete:before{content:"\F044"}.van-icon-descending:before{content:"\F045"}.van-icon-description:before{content:"\F046"}.van-icon-desktop-o:before{content:"\F047"}.van-icon-diamond-o:before{content:"\F048"}.van-icon-diamond:before{content:"\F049"}.van-icon-discount:before{content:"\F04A"}.van-icon-down:before{content:"\F04B"}.van-icon-ecard-pay:before{content:"\F04C"}.van-icon-edit:before{content:"\F04D"}.van-icon-ellipsis:before{content:"\F04E"}.van-icon-empty:before{content:"\F04F"}.van-icon-envelop-o:before{content:"\F050"}.van-icon-exchange:before{content:"\F051"}.van-icon-expand-o:before{content:"\F052"}.van-icon-expand:before{content:"\F053"}.van-icon-eye-o:before{content:"\F054"}.van-icon-eye:before{content:"\F055"}.van-icon-fail:before{content:"\F056"}.van-icon-failure:before{content:"\F057"}.van-icon-filter-o:before{content:"\F058"}.van-icon-fire-o:before{content:"\F059"}.van-icon-fire:before{content:"\F05A"}.van-icon-flag-o:before{content:"\F05B"}.van-icon-flower-o:before{content:"\F05C"}.van-icon-free-postage:before{content:"\F05D"}.van-icon-friends-o:before{content:"\F05E"}.van-icon-friends:before{content:"\F05F"}.van-icon-gem-o:before{content:"\F060"}.van-icon-gem:before{content:"\F061"}.van-icon-gift-card-o:before{content:"\F062"}.van-icon-gift-card:before{content:"\F063"}.van-icon-gift-o:before{content:"\F064"}.van-icon-gift:before{content:"\F065"}.van-icon-gold-coin-o:before{content:"\F066"}.van-icon-gold-coin:before{content:"\F067"}.van-icon-good-job-o:before{content:"\F068"}.van-icon-good-job:before{content:"\F069"}.van-icon-goods-collect-o:before{content:"\F06A"}.van-icon-goods-collect:before{content:"\F06B"}.van-icon-graphic:before{content:"\F06C"}.van-icon-home-o:before{content:"\F06D"}.van-icon-hot-o:before{content:"\F06E"}.van-icon-hot-sale-o:before{content:"\F06F"}.van-icon-hot-sale:before{content:"\F070"}.van-icon-hot:before{content:"\F071"}.van-icon-hotel-o:before{content:"\F072"}.van-icon-idcard:before{content:"\F073"}.van-icon-info-o:before{content:"\F074"}.van-icon-info:before{content:"\F075"}.van-icon-invition:before{content:"\F076"}.van-icon-label-o:before{content:"\F077"}.van-icon-label:before{content:"\F078"}.van-icon-like-o:before{content:"\F079"}.van-icon-like:before{content:"\F07A"}.van-icon-live:before{content:"\F07B"}.van-icon-location-o:before{content:"\F07C"}.van-icon-location:before{content:"\F07D"}.van-icon-lock:before{content:"\F07E"}.van-icon-logistics:before{content:"\F07F"}.van-icon-manager-o:before{content:"\F080"}.van-icon-manager:before{content:"\F081"}.van-icon-map-marked:before{content:"\F082"}.van-icon-medel-o:before{content:"\F083"}.van-icon-medel:before{content:"\F084"}.van-icon-more-o:before{content:"\F085"}.van-icon-more:before{content:"\F086"}.van-icon-music-o:before{content:"\F087"}.van-icon-music:before{content:"\F088"}.van-icon-new-arrival-o:before{content:"\F089"}.van-icon-new-arrival:before{content:"\F08A"}.van-icon-new-o:before{content:"\F08B"}.van-icon-new:before{content:"\F08C"}.van-icon-newspaper-o:before{content:"\F08D"}.van-icon-notes-o:before{content:"\F08E"}.van-icon-orders-o:before{content:"\F08F"}.van-icon-other-pay:before{content:"\F090"}.van-icon-paid:before{content:"\F091"}.van-icon-passed:before{content:"\F092"}.van-icon-pause-circle-o:before{content:"\F093"}.van-icon-pause-circle:before{content:"\F094"}.van-icon-pause:before{content:"\F095"}.van-icon-peer-pay:before{content:"\F096"}.van-icon-pending-payment:before{content:"\F097"}.van-icon-phone-circle-o:before{content:"\F098"}.van-icon-phone-circle:before{content:"\F099"}.van-icon-phone-o:before{content:"\F09A"}.van-icon-phone:before{content:"\F09B"}.van-icon-photo-o:before{content:"\F09C"}.van-icon-photo:before{content:"\F09D"}.van-icon-photograph:before{content:"\F09E"}.van-icon-play-circle-o:before{content:"\F09F"}.van-icon-play-circle:before{content:"\F0A0"}.van-icon-play:before{content:"\F0A1"}.van-icon-plus:before{content:"\F0A2"}.van-icon-point-gift-o:before{content:"\F0A3"}.van-icon-point-gift:before{content:"\F0A4"}.van-icon-points:before{content:"\F0A5"}.van-icon-printer:before{content:"\F0A6"}.van-icon-qr-invalid:before{content:"\F0A7"}.van-icon-qr:before{content:"\F0A8"}.van-icon-question-o:before{content:"\F0A9"}.van-icon-question:before{content:"\F0AA"}.van-icon-records:before{content:"\F0AB"}.van-icon-refund-o:before{content:"\F0AC"}.van-icon-replay:before{content:"\F0AD"}.van-icon-scan:before{content:"\F0AE"}.van-icon-search:before{content:"\F0AF"}.van-icon-send-gift-o:before{content:"\F0B0"}.van-icon-send-gift:before{content:"\F0B1"}.van-icon-service-o:before{content:"\F0B2"}.van-icon-service:before{content:"\F0B3"}.van-icon-setting-o:before{content:"\F0B4"}.van-icon-setting:before{content:"\F0B5"}.van-icon-share:before{content:"\F0B6"}.van-icon-shop-collect-o:before{content:"\F0B7"}.van-icon-shop-collect:before{content:"\F0B8"}.van-icon-shop-o:before{content:"\F0B9"}.van-icon-shop:before{content:"\F0BA"}.van-icon-shopping-cart-o:before{content:"\F0BB"}.van-icon-shopping-cart:before{content:"\F0BC"}.van-icon-shrink:before{content:"\F0BD"}.van-icon-sign:before{content:"\F0BE"}.van-icon-smile-comment-o:before{content:"\F0BF"}.van-icon-smile-comment:before{content:"\F0C0"}.van-icon-smile-o:before{content:"\F0C1"}.van-icon-smile:before{content:"\F0C2"}.van-icon-star-o:before{content:"\F0C3"}.van-icon-star:before{content:"\F0C4"}.van-icon-stop-circle-o:before{content:"\F0C5"}.van-icon-stop-circle:before{content:"\F0C6"}.van-icon-stop:before{content:"\F0C7"}.van-icon-success:before{content:"\F0C8"}.van-icon-thumb-circle-o:before{content:"\F0C9"}.van-icon-thumb-circle:before{content:"\F0CA"}.van-icon-todo-list-o:before{content:"\F0CB"}.van-icon-todo-list:before{content:"\F0CC"}.van-icon-tosend:before{content:"\F0CD"}.van-icon-tv-o:before{content:"\F0CE"}.van-icon-umbrella-circle:before{content:"\F0CF"}.van-icon-underway-o:before{content:"\F0D0"}.van-icon-underway:before{content:"\F0D1"}.van-icon-upgrade:before{content:"\F0D2"}.van-icon-user-circle-o:before{content:"\F0D3"}.van-icon-user-o:before{content:"\F0D4"}.van-icon-video-o:before{content:"\F0D5"}.van-icon-video:before{content:"\F0D6"}.van-icon-vip-card-o:before{content:"\F0D7"}.van-icon-vip-card:before{content:"\F0D8"}.van-icon-volume-o:before{content:"\F0D9"}.van-icon-volume:before{content:"\F0DA"}.van-icon-wap-home-o:before{content:"\F0DB"}.van-icon-wap-home:before{content:"\F0DC"}.van-icon-wap-nav:before{content:"\F0DD"}.van-icon-warn-o:before{content:"\F0DE"}.van-icon-warning-o:before{content:"\F0DF"}.van-icon-warning:before{content:"\F0E0"}.van-icon-weapp-nav:before{content:"\F0E1"}.van-icon-wechat:before{content:"\F0E2"}.van-icon-youzan-shield:before{content:"\F0E3"}:host{display:-webkit-inline-flex;display:inline-flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}.van-icon--image{width:1em;height:1em}.van-icon__image{width:100%;height:100%}.van-icon__info{z-index:1} \ No newline at end of file diff --git a/litemall-wx/pages/checkout/checkout.js b/litemall-wx/pages/checkout/checkout.js index 1fe21e419..fb09d7e7b 100644 --- a/litemall-wx/pages/checkout/checkout.js +++ b/litemall-wx/pages/checkout/checkout.js @@ -162,43 +162,24 @@ Page({ }); return } - util.request(api.OrderPrepay, { + util.request(api.OrderMockPay, { orderId: orderId }, 'POST').then(function(res) { if (res.errno === 0) { - const payParam = res.data; - console.log("支付过程开始"); - wx.requestPayment({ - 'timeStamp': payParam.timeStamp, - 'nonceStr': payParam.nonceStr, - 'package': payParam.packageValue, - 'signType': payParam.signType, - 'paySign': payParam.paySign, - 'success': function(res) { - console.log("支付过程成功"); - if (grouponLinkId) { - setTimeout(() => { - wx.redirectTo({ - url: '/pages/groupon/grouponDetail/grouponDetail?id=' + grouponLinkId - }) - }, 1000); - } else { - wx.redirectTo({ - url: '/pages/payResult/payResult?status=1&orderId=' + orderId - }); - } - }, - 'fail': function(res) { - console.log("支付过程失败"); + console.log("模拟支付成功"); + if (grouponLinkId) { + setTimeout(() => { wx.redirectTo({ - url: '/pages/payResult/payResult?status=0&orderId=' + orderId - }); - }, - 'complete': function(res) { - console.log("支付过程结束") - } - }); + url: '/pages/groupon/grouponDetail/grouponDetail?id=' + grouponLinkId + }) + }, 1000); + } else { + wx.redirectTo({ + url: '/pages/payResult/payResult?status=1&orderId=' + orderId + }); + } } else { + util.showErrorToast(res.errmsg || '支付失败'); wx.redirectTo({ url: '/pages/payResult/payResult?status=0&orderId=' + orderId }); diff --git a/litemall-wx/pages/payResult/payResult.js b/litemall-wx/pages/payResult/payResult.js index 9ced6f794..c4127d848 100644 --- a/litemall-wx/pages/payResult/payResult.js +++ b/litemall-wx/pages/payResult/payResult.js @@ -31,32 +31,16 @@ Page({ }, payOrder() { let that = this; - util.request(api.OrderPrepay, { + util.request(api.OrderMockPay, { orderId: that.data.orderId }, 'POST').then(function(res) { if (res.errno === 0) { - const payParam = res.data; - console.log("支付过程开始") - wx.requestPayment({ - 'timeStamp': payParam.timeStamp, - 'nonceStr': payParam.nonceStr, - 'package': payParam.packageValue, - 'signType': payParam.signType, - 'paySign': payParam.paySign, - 'success': function(res) { - console.log("支付过程成功") - that.setData({ - status: true - }); - }, - 'fail': function(res) { - console.log("支付过程失败") - util.showErrorToast('支付失败'); - }, - 'complete': function(res) { - console.log("支付过程结束") - } + console.log("模拟支付成功") + that.setData({ + status: true }); + } else { + util.showErrorToast(res.errmsg || '支付失败'); } }); } diff --git a/litemall-wx/project.config.json b/litemall-wx/project.config.json index 0a6c5e06a..c0ff8bd97 100644 --- a/litemall-wx/project.config.json +++ b/litemall-wx/project.config.json @@ -32,11 +32,17 @@ "minifyWXML": true, "showES6CompileOption": false, "useCompilerPlugins": false, - "ignoreUploadUnusedFiles": true + "ignoreUploadUnusedFiles": true, + "compileWorklet": false, + "enhance": false, + "localPlugins": false, + "condition": false, + "swc": false, + "disableSWC": true }, "compileType": "miniprogram", "libVersion": "2.4.0", - "appid": "wxa5b486c6b918ecfb", + "appid": "wxaa18b36dce8ac9a5", "projectname": "litemall-wx", "simulatorType": "wechat", "simulatorPluginLibVersion": {}, @@ -323,5 +329,10 @@ } ] } - } + }, + "packOptions": { + "ignore": [], + "include": [] + }, + "editorSetting": {} } \ No newline at end of file diff --git a/litemall-wx/project.private.config.json b/litemall-wx/project.private.config.json index 4a98289a6..e44893108 100644 --- a/litemall-wx/project.private.config.json +++ b/litemall-wx/project.private.config.json @@ -1,282 +1,24 @@ { - "setting": {}, - "condition": { - "plugin": { - "list": [] - }, - "game": { - "list": [] - }, - "gamePlugin": { - "list": [] - }, - "miniprogram": { - "list": [ - { - "id": -1, - "name": "首页", - "pathName": "pages/index/index", - "query": "" - }, - { - "id": -1, - "name": "专题", - "pathName": "pages/topic/topic", - "query": "" - }, - { - "id": -1, - "name": "专题详情", - "pathName": "pages/topicDetail/topicDetail", - "query": "id=314" - }, - { - "id": -1, - "name": "专题评论列表", - "pathName": "pages/topicComment/topicComment", - "query": "valueId=314&type=1" - }, - { - "id": -1, - "name": "专题评论添加", - "pathName": "pages/topicCommentPost/topicCommentPost", - "query": "valueId=314&type=1" - }, - { - "id": -1, - "name": "品牌", - "pathName": "pages/brand/brand", - "query": "" - }, - { - "id": -1, - "name": "品牌详情", - "pathName": "pages/brandDetail/brandDetail", - "query": "id=1001000" - }, - { - "id": -1, - "name": "人气推荐", - "pathName": "pages/hotGoods/hotGoods", - "query": "" - }, - { - "id": -1, - "name": "新品首发", - "pathName": "pages/newGoods/newGoods", - "query": "" - }, - { - "id": -1, - "name": "分类", - "pathName": "pages/catalog/catalog", - "query": "" - }, - { - "id": -1, - "name": "分类详情", - "pathName": "pages/category/category", - "query": "id=1008002" - }, - { - "id": -1, - "name": "查找", - "pathName": "pages/search/search", - "query": "" - }, - { - "name": "商品", - "pathName": "pages/goods/goods", - "query": "id=1181267", - "scene": null, - "launchMode": "default" - }, - { - "id": -1, - "name": "商品评论列表", - "pathName": "pages/comment/comment", - "query": "valueId=1181000&type=0" - }, - { - "id": -1, - "name": "购物车", - "pathName": "pages/cart/cart", - "query": "" - }, - { - "id": -1, - "name": "下单", - "pathName": "pages/checkout/checkout", - "query": "" - }, - { - "id": -1, - "name": "支付结果", - "pathName": "pages/payResult/payResult", - "query": "" - }, - { - "id": -1, - "name": "我的", - "pathName": "pages/ucenter/index/index", - "query": "" - }, - { - "id": -1, - "name": "我的订单列表", - "pathName": "pages/ucenter/order/order", - "query": "" - }, - { - "id": -1, - "name": "我的订单详情", - "pathName": "pages/ucenter/orderDetail/orderDetail", - "query": "id=1" - }, - { - "id": 22, - "name": "待评价的订单详情", - "pathName": "pages/ucenter/orderDetail/orderDetail", - "query": "id=1" - }, - { - "id": -1, - "name": "购买商品评价", - "pathName": "pages/commentPost/commentPost", - "query": "orderId=2&type=0&valueId=1116011" - }, - { - "id": 22, - "name": "我的优惠券", - "pathName": "pages/ucenter/couponList/couponList", - "query": "" - }, - { - "id": -1, - "name": "我的收藏", - "pathName": "pages/ucenter/collect/collect", - "query": "" - }, - { - "id": -1, - "name": "我的足迹", - "pathName": "pages/ucenter/footprint/footprint", - "query": "" - }, - { - "id": -1, - "name": "我的地址", - "pathName": "pages/ucenter/address/address", - "query": "" - }, - { - "id": -1, - "name": "我的地址添加", - "pathName": "pages/ucenter/addressAdd/addressAdd", - "query": "" - }, - { - "id": -1, - "name": "登录", - "pathName": "pages/auth/login/login", - "query": "" - }, - { - "id": -1, - "name": "账号登录", - "pathName": "pages/auth/accountLogin/accountLogin", - "query": "" - }, - { - "id": -1, - "name": "注册", - "pathName": "pages/auth/register/register", - "query": "" - }, - { - "id": -1, - "name": "找回密码", - "pathName": "pages/auth/reset/reset", - "query": "" - }, - { - "id": -1, - "name": "关于", - "pathName": "pages/about/about", - "query": "" - }, - { - "id": -1, - "name": "测试更新", - "pathName": "pages/index/index", - "query": "" - }, - { - "id": -1, - "name": "意见反馈", - "pathName": "pages/ucenter/feedback/feedback", - "query": "" - }, - { - "id": -1, - "name": "团购专区", - "pathName": "pages/groupon/grouponList/grouponList", - "query": "" - }, - { - "id": -1, - "name": "选择优惠券", - "pathName": "pages/ucenter/couponSelect/couponSelect", - "query": "" - }, - { - "id": -1, - "name": "我的优惠券列表", - "pathName": "pages/ucenter/couponList/couponList", - "query": "" - }, - { - "id": -1, - "name": "优惠券列表", - "pathName": "pages/coupon/coupon", - "query": "" - }, - { - "id": -1, - "name": "帮助中心", - "pathName": "pages/help/help", - "query": "", - "scene": null - }, - { - "id": -1, - "name": "我的团购", - "pathName": "pages/groupon/myGroupon/myGroupon", - "query": "", - "scene": null - }, - { - "id": -1, - "name": "申请售后", - "pathName": "pages/ucenter/aftersale/aftersale", - "query": "id=2", - "scene": null - }, - { - "id": -1, - "name": "售后列表", - "pathName": "pages/ucenter/aftersaleList/aftersaleList", - "query": "", - "scene": null - }, - { - "id": -1, - "name": "售后详情", - "pathName": "pages/ucenter/aftersaleDetail/aftersaleDetail", - "query": "id=1", - "scene": null - } - ] - } - } + "setting": { + "urlCheck": false, + "coverView": true, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": false, + "preloadBackgroundData": false, + "autoAudits": false, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": true, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "compileHotReLoad": true, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true, + "bigPackageSizeSupport": false, + "useIsolateContext": true + }, + "condition": {}, + "libVersion": "3.17.4", + "projectname": "litemall-wx" } \ No newline at end of file diff --git a/start.bat b/start.bat new file mode 100644 index 000000000..f61e10c8a --- /dev/null +++ b/start.bat @@ -0,0 +1,94 @@ +@echo off +setlocal enabledelayedexpansion +title litemall һ¼üÆô¶¯ +cd /d "%~dp0" + +echo ================================================ +echo litemall É̳Ç һ¼üÆô¶¯ +echo ================================================ +echo. + +REM ---- 1. ¼ì²é MySQL ·þÎñ ---- +sc query MySQL80 | find "RUNNING" >nul 2>&1 +if errorlevel 1 ( + echo [1/5] MySQL80 δÔËÐУ¬ÕýÔÚÆô¶¯... + net start MySQL80 >nul 2>&1 + if errorlevel 1 ( + echo [´íÎó] MySQL Æô¶¯ʧ°ܣ¬ÇëÊֶ¯Æô¶¯ºóÖØÊԡ£ + goto :end + ) +) +echo [1/5] MySQL80 ÔËÐÐÖÐ OK + +REM ---- 2. ¼ì²éºó¶Ë jar£¬ȱʧÔ򹹽¨ ---- +if not exist "litemall-all\target\litemall-all-0.1.0-exec.jar" ( + echo [2/5] δÕҵ½ºó¶Ë jar£¬¿ªʼ¹¹½¨£¨mvn clean package£©... + call mvn clean package -Dmaven.test.skip=true + if errorlevel 1 ( + echo [´íÎó] ºó¶˹¹½¨ʧ°ܣ¬Çë¼ì²é Maven/ÍøÂ硣 + goto :end + ) +) +echo [2/5] ºó¶Ë jar ¾ÍÐ÷ OK + +REM ---- 3. Æô¶¯ºó¶Ë 8080 ---- +netstat -ano | findstr ":8080" | findstr "LISTENING" >nul 2>&1 +if errorlevel 1 ( + echo [3/5] Æô¶¯ºó¶Ë 8080 ... + start "litemall-backend-8080" cmd /k "cd /d %~dp0 && java -Dfile.encoding=UTF-8 -jar litemall-all\target\litemall-all-0.1.0-exec.jar" +) else ( + echo [3/5] ºó¶Ë 8080 ÒÑÔÚÔËÐÐ +) + +REM ---- 4. Æô¶¯¹ÜÀíºǫ́ 9527 ---- +netstat -ano | findstr ":9527" | findstr "LISTENING" >nul 2>&1 +if errorlevel 1 ( + echo [4/5] Æô¶¯¹ÜÀíºǫ́ 9527 ... + start "litemall-admin-9527" cmd /k "cd /d %~dp0litemall-admin && set NODE_OPTIONS=--openssl-legacy-provider&& npm run dev" +) else ( + echo [4/5] ¹ÜÀíºǫ́ 9527 ÒÑÔÚÔËÐÐ +) + +REM ---- 5. Æô¶¯ÇáÉ̳Ç 6255 ---- +netstat -ano | findstr ":6255" | findstr "LISTENING" >nul 2>&1 +if errorlevel 1 ( + echo [5/5] Æô¶¯ÇáÉ̳Ç 6255 ... + start "litemall-vue-6255" cmd /k "cd /d %~dp0litemall-vue && set NODE_OPTIONS=--openssl-legacy-provider&& npm run dev" +) else ( + echo [5/5] ÇáÉ̳Ç 6255 ÒÑÔÚÔËÐÐ +) + +echo. +echo ÕýÔڵȴý·þÎñ¾ÍÐ÷£¨ǰ¶ËÊ״αàÒëԼÐè 30~60 Ã룬ÇëÎð¹رյ¯³öµĴ°¿ڣ©... +echo 8080=ºó¶Ë 9527=¹ÜÀíºǫ́ 6255=ÇáÉ̳Ç +echo. + +call :waitport 8080 60 +call :waitport 9527 90 + +echo. +echo ¹ÜÀíºǫ́ÒѾÍÐ÷£¬ÕýÔڴò¿ªä¯ÀÀÆ÷ http://localhost:9527 +start http://localhost:9527 +echo ÇáÉ̳ǵØַ£ºhttp://localhost:6255 + +goto :end + +:waitport +REM ²ÎÊý£º%1=¶˿Ú %2=×î´óµȴýÃëÊý +set /a _n=0 +:waitloop +timeout /t 3 /nobreak >nul +netstat -ano | findstr ":%1" | findstr "LISTENING" >nul 2>&1 +if not errorlevel 1 goto :waitok +set /a _n+=3 +if !_n! lss %2 goto :waitloop +echo [Ìáʾ] ¶˿Ú %1 ÔÚ %2 ÃëÄÚδ¾ÍÐ÷£¬ÇëÉԺóÊֶ¯ˢÐÂҳÃ档 +goto :eof +:waitok +echo [OK] ¶˿Ú %1 ÒѾÍÐ÷ +goto :eof + +:end +echo. +echo Íê³ɡ£¸÷·þÎñÔËÐÐÔڶÀÁ¢´°¿ÚÖУ¬¹رնÔӦ´°¿ڼ´ֹͣ¸÷þÎñ¡£ +pause >nul diff --git a/stop.bat b/stop.bat new file mode 100644 index 000000000..e0bd13403 --- /dev/null +++ b/stop.bat @@ -0,0 +1,15 @@ +@echo off +setlocal +title litemall ֹͣ·þÎñ +echo ÕýÔÚֹͣ litemall ·þÎ񣨶˿Ú 8080 / 9527 / 6255£©... + +for %%p in (8080 9527 6255) do ( + for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%%p" ^| findstr "LISTENING"') do ( + echo ֹͣ¶˿Ú %%p (PID %%a) + taskkill /f /pid %%a >nul 2>&1 + ) +) + +echo. +echo Íê³ɡ££¨MySQL ·þÎñδֹͣ£¬ÈçÐèֹͣÇëÊֶ¯ִÐÐ net stop MySQL80£© +pause >nul From bc74a4634b47566e3c1276e1b5a57b6f14679b77 Mon Sep 17 00:00:00 2001 From: yonghuwh <2132616158@qq.com> Date: Tue, 13 Oct 2026 17:10:25 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix:=E7=A7=BB=E9=99=A4=E6=95=8F=E6=84=9F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=B9=B6=E6=9B=B4=E6=96=B0=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + litemall-wx/project.config.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a38cc562d..cfc97c4f4 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ Servers .metadata upload gen_code +litemall-wx/project.config.json diff --git a/litemall-wx/project.config.json b/litemall-wx/project.config.json index c0ff8bd97..d04de34fc 100644 --- a/litemall-wx/project.config.json +++ b/litemall-wx/project.config.json @@ -42,7 +42,7 @@ }, "compileType": "miniprogram", "libVersion": "2.4.0", - "appid": "wxaa18b36dce8ac9a5", + "appid": "YOUR_APPID_HEME", "projectname": "litemall-wx", "simulatorType": "wechat", "simulatorPluginLibVersion": {}, From 0bab0757c0882bdec36745e791f8989d63843fa2 Mon Sep 17 00:00:00 2001 From: Look4u-1 <2529702675@qq.com> Date: Fri, 16 Oct 2026 00:12:34 +0800 Subject: [PATCH 5/6] =?UTF-8?q?docs(core):=20=E5=AE=8C=E5=96=84=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E4=B8=8E=E7=89=A9=E6=B5=81=E6=A8=A1=E5=9D=97=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../litemall/core/express/ExpressService.java | 62 ++++++++++--------- .../config/ExpressAutoConfiguration.java | 8 +++ .../express/config/ExpressProperties.java | 8 +++ .../core/express/dao/ExpressInfo.java | 16 +++-- .../litemall/core/express/dao/Traces.java | 9 +-- .../litemall/core/storage/AliyunStorage.java | 36 +++++++++-- .../litemall/core/storage/LocalStorage.java | 36 ++++++++++- .../litemall/core/storage/QiniuStorage.java | 35 ++++++++++- .../litemall/core/storage/Storage.java | 38 +++++++++--- .../litemall/core/storage/StorageService.java | 48 +++++++++++--- .../litemall/core/storage/TencentStorage.java | 41 ++++++++++-- .../config/StorageAutoConfiguration.java | 26 ++++++++ .../storage/config/StorageProperties.java | 19 ++++++ 13 files changed, 322 insertions(+), 60 deletions(-) diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/ExpressService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/ExpressService.java index d6438da63..0d15c72de 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/ExpressService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/ExpressService.java @@ -15,14 +15,14 @@ import java.util.Map; /** - * 物流查询服务 - *

- * 快递鸟即时查询API http://www.kdniao.com/api-track + * 封装快递鸟即时查询,为订单详情提供物流轨迹及快递公司名称。 + *

关闭开关或请求、解析异常时返回 null;非空响应仍须检查 Success, + * 不能把返回对象直接当作查询成功。 */ public class ExpressService { private final Log logger = LogFactory.getLog(ExpressService.class); - //请求url + // 服务商即时查询入口;enable 为 false 时不会发送请求。 private String ReqURL = "http://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx"; private ExpressProperties properties; @@ -36,10 +36,9 @@ public void setProperties(ExpressProperties properties) { } /** - * 获取物流供应商名 - * - * @param vendorCode - * @return + * 将订单中的快递公司编码转换为页面展示名称。 + * @param vendorCode 与 vendors 配置中 code 对应的公司编码 + * @return 配置中的公司名称;没有匹配项时返回 null */ public String getVendorName(String vendorCode) { for (Map item : properties.getVendors()) { @@ -49,16 +48,20 @@ public String getVendorName(String vendorCode) { return null; } + /** + * 提供可选快递公司,供后台发货时选择。 + * @return 配置中的公司列表,每项包含 code 和 name;返回原列表而非副本 + */ public List> getVendors() { return properties.getVendors(); } /** - * 获取物流信息 - * - * @param expCode - * @param expNo - * @return + * 查询物流轨迹并补充本地快递公司名称。 + *

关闭查询或发生异常时返回 null;上游业务失败仍可能返回 Success=false 的对象。 + * @param expCode 快递公司编码,应与上游协议及本地 vendors 配置一致 + * @param expNo 运单号,保留字符串以避免丢失前导零 + * @return 查询响应;未启用或请求、解析异常时返回 null */ public ExpressInfo getExpressInfo(String expCode, String expNo) { if (!properties.isEnable()) { @@ -79,9 +82,12 @@ public ExpressInfo getExpressInfo(String expCode, String expNo) { } /** - * Json方式 查询订单物流轨迹 - * - * @throws Exception + * 按现有快递鸟即时查询协议提交运单查询。 + *

签名基于未 URL 编码的请求数据计算,再分别对请求数据和签名编码。 + * @param expCode 快递公司编码 + * @param expNo 运单号 + * @return 服务商返回的原始 JSON 字符串 + * @throws Exception 请求参数编码等处理发生异常时 */ private String getOrderTracesByJson(String expCode, String expNo) throws Exception { String requestData = "{'OrderCode':'','ShipperCode':'" + expCode + "','LogisticCode':'" + expNo + "'}"; @@ -89,6 +95,7 @@ private String getOrderTracesByJson(String expCode, String expNo) throws Excepti Map params = new HashMap(); params.put("RequestData", URLEncoder.encode(requestData, "UTF-8")); params.put("EBusinessID", properties.getAppId()); + // 1002 为本实现采用的即时物流查询类型,2 表示 JSON 响应。 params.put("RequestType", "1002"); String dataSign = encrypt(requestData, properties.getAppKey(), "UTF-8"); params.put("DataSign", URLEncoder.encode(dataSign, "UTF-8")); @@ -96,17 +103,16 @@ private String getOrderTracesByJson(String expCode, String expNo) throws Excepti String result = HttpUtil.sendPost(ReqURL, params); - //根据公司业务处理返回的信息...... return result; } /** - * MD5加密 - * - * @param str 内容 - * @param charset 编码方式 - * @throws Exception + * 计算协议签名需要的小写十六进制 MD5 摘要,不用于密码存储或内容加密。 + * @param str 待摘要的请求数据与密钥拼接结果 + * @param charset 将字符串转换为字节时使用的字符集 + * @return 32 位小写十六进制摘要 + * @throws Exception 算法不可用或字符集不受支持时 */ private String MD5(String str, String charset) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); @@ -124,12 +130,12 @@ private String MD5(String str, String charset) throws Exception { } /** - * Sign签名生成 - * - * @param content 内容 - * @param keyValue Appkey - * @param charset 编码方式 - * @return DataSign签名 + * 将请求数据与 AppKey 拼接后的 MD5 十六进制字符串进行 Base64 编码。 + *

这里编码的是摘要字符串的字节,不是 MD5 原始摘要字节;需保持协议顺序。 + * @param content 未经 URL 编码的请求内容 + * @param keyValue 服务商 AppKey;为 null 时当前实现不拼接密钥 + * @param charset 摘要及字符串转换使用的字符集 + * @return 未 URL 编码的签名;计算异常时返回 null */ private String encrypt(String content, String keyValue, String charset) { if (keyValue != null) { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressAutoConfiguration.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressAutoConfiguration.java index 4e7458ffe..9bb10f8f1 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressAutoConfiguration.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressAutoConfiguration.java @@ -5,6 +5,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * 将物流配置绑定到 ExpressService,供订单查询复用统一服务。 + *

服务 Bean 始终创建,是否发送外部请求由 ExpressService 的 enable 检查决定。 + */ @Configuration @EnableConfigurationProperties(ExpressProperties.class) public class ExpressAutoConfiguration { @@ -15,6 +19,10 @@ public ExpressAutoConfiguration(ExpressProperties properties) { this.properties = properties; } + /** + * 将配置交给物流服务,在查询时统一判断启用状态。 + * @return 已绑定配置的物流查询服务 + */ @Bean public ExpressService expressService() { ExpressService expressService = new ExpressService(); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressProperties.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressProperties.java index 034cf963c..5bb02c333 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressProperties.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/config/ExpressProperties.java @@ -6,11 +6,19 @@ import java.util.List; import java.util.Map; +/** + * 绑定 litemall.express 配置,集中管理物流开关、查询凭据与快递公司列表。 + *

enable 默认 false,vendors 默认空列表;启用前需配置服务商所需凭据。 + */ @ConfigurationProperties(prefix = "litemall.express") public class ExpressProperties { + /** 是否启用外部物流查询,未配置时为 false。 */ private boolean enable; + /** 快递鸟商户标识,用于请求中的 EBusinessID。 */ private String appId; + /** 快递鸟签名密钥,仅由部署配置注入。 */ private String appKey; + /** 快递公司配置,每项含 code 与 name,默认空列表。 */ private List> vendors = new ArrayList<>(); public boolean isEnable() { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java index d281385da..0c880ff3b 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java @@ -8,28 +8,36 @@ import java.util.List; /** - * Auto-generated: 2018-07-19 22:27:22 - * - * @author bejson.com (i@bejson.com) - * @website http://www.bejson.com/java2pojo/ + * 快递鸟物流查询响应,保留上游字段映射以兼容现有反序列化。 + *

Success 表示查询是否成功,Reason 提供失败原因;ShipperName 由本地配置补充。 + * @author bejson.com (原始模型生成来源) */ public class ExpressInfo { @JsonProperty("LogisticCode") + /** 运单号,保留服务商返回的字符串。 */ private String LogisticCode; @JsonProperty("ShipperCode") + /** 服务商快递公司编码。 */ private String ShipperCode; @JsonProperty("Traces") + /** 物流轨迹节点;查询无结果时可能为空或 null。 */ private List Traces; @JsonProperty("State") + /** 服务商物流状态原值,当前类不转换为本地枚举。 */ private String State; @JsonProperty("EBusinessID") + /** 响应关联的商户标识。 */ private String EBusinessID; @JsonProperty("Success") + /** 服务商业务查询结果,不能仅凭 HTTP 请求完成判断成功。 */ private boolean Success; @JsonProperty("Reason") + /** 服务商返回的失败或补充说明。 */ private String Reason; + /** 依据本地 vendors 配置补充的公司名称。 */ + private String ShipperName; public String getLogisticCode() { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java index 24a070de9..99f95202b 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java @@ -6,16 +6,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Auto-generated: 2018-07-19 22:27:22 - * - * @author bejson.com (i@bejson.com) - * @website http://www.bejson.com/java2pojo/ + * 物流轨迹中的一个节点,描述某一时间发生的揽收、转运或签收事件。 + *

时间保留服务商原始字符串,当前模型不进行时区或日期格式转换。 + * @author bejson.com (原始模型生成来源) */ public class Traces { @JsonProperty("AcceptStation") + /** 轨迹发生地点及事件说明。 */ private String AcceptStation; @JsonProperty("AcceptTime") + /** 服务商返回的事件时间字符串。 */ private String AcceptTime; public String getAcceptStation() { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/AliyunStorage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/AliyunStorage.java index 30d84b00e..6164f8076 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/AliyunStorage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/AliyunStorage.java @@ -16,9 +16,9 @@ import java.util.stream.Stream; /** + * 阿里云 OSS 存储适配器,以 bucket 和对象键定位商城文件。 + *

上传和删除异常仅写入日志,不向调用方报告失败;不提供本地路径与目录枚举。 * @author Yogeek - * @date 2018/7/16 16:10 - * @decrpt 阿里云对象存储服务 */ public class AliyunStorage implements Storage { @@ -75,12 +75,17 @@ private String getBaseUrl() { } /** - * 阿里云OSS对象存储简单上传实现 + * 保存文件内容,以对象键而非用户原始文件名定位对象。 + *

当前实现捕获上传异常并记录日志,正常返回不能作为上传成功凭据。 + * @param inputStream 文件内容输入流 + * @param contentLength 文件字节数 + * @param contentType 文件 MIME 类型 + * @param keyName 由调用方生成的可信对象键 */ @Override public void store(InputStream inputStream, long contentLength, String contentType, String keyName) { try { - // 简单文件上传, 最大支持 5 GB, 适用于小文件上传, 建议 20M以下的文件使用该接口 + // 设置长度和 MIME 类型,供对象存储识别内容及下游浏览器展示。 ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(contentLength); objectMetadata.setContentType(contentType); @@ -93,16 +98,30 @@ public void store(InputStream inputStream, long contentLength, String contentTyp } + /** + * 当前云适配器不实现目录枚举。 + * @return null + */ @Override public Stream loadAll() { return null; } + /** + * 当前云适配器不提供本地文件路径。 + * @param keyName 对象键 + * @return null + */ @Override public Path load(String keyName) { return null; } + /** + * 将对象键转换为可读取的资源,供文件下载使用。 + * @param keyName 存储对象键 + * @return 可用资源;地址无效或不可访问时可能返回 null + */ @Override public Resource loadAsResource(String keyName) { try { @@ -119,6 +138,10 @@ public Resource loadAsResource(String keyName) { } } + /** + * 删除底层存储对象,不负责删除数据库中的元数据记录。 + * @param keyName 待删除的对象键 + */ @Override public void delete(String keyName) { try { @@ -129,6 +152,11 @@ public void delete(String keyName) { } + /** + * 根据存储配置拼接访问地址,不检查对象存在性或生成签名凭证。 + * @param keyName 存储对象键 + * @return 对象访问地址 + */ @Override public String generateUrl(String keyName) { return getBaseUrl() + keyName; diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java index 3c182b570..9c70ed803 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java @@ -16,7 +16,9 @@ import java.util.stream.Stream; /** - * 服务器本地对象存储服务 + * 以服务器磁盘保存商品图片等文件,并通过配置的 HTTP 地址生成访问链接。 + *

路径由 storagePath 指定,URL 由 address 指定,两者需对应同一资源映射。 + * 本类不检查路径穿越;调用方必须传入可信对象键。 */ public class LocalStorage implements Storage { @@ -51,6 +53,15 @@ public void setAddress(String address) { this.address = address; } + /** + * 保存文件内容,以对象键而非用户原始文件名定位对象。 + *

同名目标会被覆盖;contentLength 和 contentType 不参与磁盘复制。 + * @param inputStream 文件内容输入流 + * @param contentLength 文件字节数 + * @param contentType 文件 MIME 类型 + * @param keyName 由调用方生成的可信对象键 + * @throws RuntimeException 文件复制发生 I/O 错误时 + */ @Override public void store(InputStream inputStream, long contentLength, String contentType, String keyName) { try { @@ -60,6 +71,10 @@ public void store(InputStream inputStream, long contentLength, String contentTyp } } + /** + * 列出存储根目录下一层的相对路径。 + * @return 路径流,调用方使用后应关闭;云实现可能返回 null + */ @Override public Stream loadAll() { try { @@ -72,11 +87,21 @@ public Stream loadAll() { } + /** + * 按对象键定位文件;本方法不验证文件是否存在。 + * @param filename 存储对象键 + * @return 本地路径;云实现可能返回 null + */ @Override public Path load(String filename) { return rootLocation.resolve(filename); } + /** + * 将对象键转换为可读取的资源,供文件下载使用。 + * @param filename 存储对象键 + * @return 可用资源;地址无效或不可访问时可能返回 null + */ @Override public Resource loadAsResource(String filename) { try { @@ -93,6 +118,10 @@ public Resource loadAsResource(String filename) { } } + /** + * 删除底层存储对象,不负责删除数据库中的元数据记录。 + * @param filename 待删除的对象键 + */ @Override public void delete(String filename) { Path file = load(filename); @@ -103,6 +132,11 @@ public void delete(String filename) { } } + /** + * 根据存储配置拼接访问地址,不检查对象存在性或生成签名凭证。 + * @param keyName 存储对象键 + * @return 对象访问地址 + */ @Override public String generateUrl(String keyName) { diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/QiniuStorage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/QiniuStorage.java index 03827ec39..1671f8b76 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/QiniuStorage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/QiniuStorage.java @@ -16,6 +16,11 @@ import java.nio.file.Path; import java.util.stream.Stream; +/** + * 七牛云对象存储适配器,上传凭证由桶名及访问密钥生成。 + *

访问地址使用配置的 endpoint;SDK 管理器无同步地延迟初始化, + * 首次并发调用可能重复创建对象,配置应在服务使用前完成。 + */ public class QiniuStorage implements Storage { private final Log logger = LogFactory.getLog(QiniuStorage.class); @@ -61,7 +66,12 @@ public void setBucketName(String bucketName) { } /** - * 七牛云OSS对象存储简单上传实现 + * 保存文件内容,以对象键而非用户原始文件名定位对象。 + *

当前实现捕获上传异常并记录日志,正常返回不能作为上传成功凭据。 + * @param inputStream 文件内容输入流 + * @param contentLength 文件字节数 + * @param contentType 文件 MIME 类型 + * @param keyName 由调用方生成的可信对象键 */ @Override public void store(InputStream inputStream, long contentLength, String contentType, String keyName) { @@ -80,16 +90,30 @@ public void store(InputStream inputStream, long contentLength, String contentTyp } } + /** + * 当前云适配器不实现目录枚举。 + * @return null + */ @Override public Stream loadAll() { return null; } + /** + * 当前云适配器不提供本地文件路径。 + * @param keyName 对象键 + * @return null + */ @Override public Path load(String keyName) { return null; } + /** + * 将对象键转换为可读取的资源,供文件下载使用。 + * @param keyName 存储对象键 + * @return 可用资源;地址无效或不可访问时可能返回 null + */ @Override public Resource loadAsResource(String keyName) { try { @@ -104,6 +128,10 @@ public Resource loadAsResource(String keyName) { return null; } + /** + * 删除底层存储对象,不负责删除数据库中的元数据记录。 + * @param keyName 待删除的对象键 + */ @Override public void delete(String keyName) { if (bucketManager == null) { @@ -119,6 +147,11 @@ public void delete(String keyName) { } } + /** + * 根据存储配置拼接访问地址,不检查对象存在性或生成签名凭证。 + * @param keyName 存储对象键 + * @return 对象访问地址 + */ @Override public String generateUrl(String keyName) { return endpoint + "/" + keyName; diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java index 6c3ce0c83..5d843918c 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java @@ -7,27 +7,51 @@ import java.util.stream.Stream; /** - * 对象存储接口 + * 对象存储契约,使上传调用方不依赖本地磁盘或具体云厂商。 + *

对象键用于定位文件,不等同于原始文件名;资源可用性、异常处理和路径支持 + * 取决于实现。三个云实现的 loadAll/load 当前返回 null。 */ public interface Storage { /** - * 存储一个文件对象 - * - * @param inputStream 文件输入流 - * @param contentLength 文件长度 - * @param contentType 文件类型 - * @param keyName 文件名 + * 保存文件内容,以对象键而非用户原始文件名定位对象。 + * @param inputStream 文件内容输入流 + * @param contentLength 文件字节数 + * @param contentType 文件 MIME 类型 + * @param keyName 由调用方生成的可信对象键 */ void store(InputStream inputStream, long contentLength, String contentType, String keyName); + /** + * 枚举存储对象的相对路径,仅本地实现提供支持。 + * @return 路径流,调用方负责关闭;不支持时返回 null + */ Stream loadAll(); + /** + * 将对象键映射为本地路径,不检查存在性。 + * @param keyName 存储对象键 + * @return 本地路径;云实现返回 null + */ Path load(String keyName); + /** + * 将对象键转换为可读取的资源,供文件下载使用。 + * @param keyName 存储对象键 + * @return 可用资源;地址无效或不可访问时可能返回 null + */ Resource loadAsResource(String keyName); + /** + * 删除底层存储对象,不负责删除数据库中的元数据记录。 + * @param keyName 待删除的对象键 + */ void delete(String keyName); + /** + * 根据存储配置拼接访问地址,不检查对象存在性或生成签名凭证。 + * @param keyName 存储对象键 + * @return 对象访问地址 + */ String generateUrl(String keyName); } \ No newline at end of file diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/StorageService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/StorageService.java index 510f4c0c2..11e266a6b 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/StorageService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/StorageService.java @@ -11,7 +11,9 @@ import java.util.stream.Stream; /** - * 提供存储服务类,所有存储服务均由该类对外提供 + * 商城文件存储入口,协调对象上传与 litemall_storage 元数据登记。 + *

先调用存储实现,再写入数据库,两者不构成跨系统原子事务; + * 云实现可能仅记录上传异常,因此返回元数据不等于远端文件已经可用。 */ public class StorageService { private String active; @@ -36,12 +38,14 @@ public void setStorage(Storage storage) { } /** - * 存储一个文件对象 - * - * @param inputStream 文件输入流 - * @param contentLength 文件长度 - * @param contentType 文件类型 - * @param fileName 文件索引名 + * 生成随机对象键、上传文件,再登记文件元数据。 + *

调用方必须提供包含扩展名的原始文件名;底层上传和数据库写入不是原子操作。 + * @param inputStream 文件内容输入流 + * @param contentLength 文件字节数,登记时转换为 int,调用方应限制上传大小 + * @param contentType 文件 MIME 类型 + * @param fileName 含扩展名的原始文件名,用于显示及提取后缀 + * @return 已写入数据库的文件元数据,不代表云端上传已经确认成功 + * @throws StringIndexOutOfBoundsException 文件名不包含点号,无法提取后缀时 */ public LitemallStorage store(InputStream inputStream, long contentLength, String contentType, String fileName) { String key = generateKey(fileName); @@ -59,6 +63,13 @@ public LitemallStorage store(InputStream inputStream, long contentLength, String return storageInfo; } + /** + * 保留文件后缀并生成随机键,避免不同用户的同名文件互相覆盖。 + *

查库重试降低重复风险,但不构成并发唯一性保证。 + * @param originalFilename 含点号及后缀的原始文件名 + * @return 20 位随机字符串与原始后缀组成的对象键 + * @throws StringIndexOutOfBoundsException 文件名不含点号时 + */ private String generateKey(String originalFilename) { int index = originalFilename.lastIndexOf('.'); String suffix = originalFilename.substring(index); @@ -75,22 +86,45 @@ private String generateKey(String originalFilename) { return key; } + /** + * 委托当前存储实现枚举对象。 + * @return 本地路径流(使用后应关闭);云实现返回 null + */ public Stream loadAll() { return storage.loadAll(); } + /** + * 委托当前存储实现定位对象。 + * @param keyName 存储对象键 + * @return 本地路径;云实现返回 null + */ public Path load(String keyName) { return storage.load(keyName); } + /** + * 将对象键转换为可读取的资源,供文件下载使用。 + * @param keyName 存储对象键 + * @return 可用资源;地址无效或不可访问时可能返回 null + */ public Resource loadAsResource(String keyName) { return storage.loadAsResource(keyName); } + /** + * 删除底层存储对象,不负责删除数据库中的元数据记录。 + * @param keyName 待删除的对象键 + */ public void delete(String keyName) { storage.delete(keyName); } + /** + * 根据存储配置拼接访问地址,不检查对象存在性或生成签名凭证。 + * @param keyName 存储对象键 + * @return 对象访问地址 + */ private String generateUrl(String keyName) { return storage.generateUrl(keyName); } diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/TencentStorage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/TencentStorage.java index d899f671a..947454da8 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/TencentStorage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/TencentStorage.java @@ -19,7 +19,9 @@ import java.util.stream.Stream; /** - * 腾讯对象存储服务 + * 腾讯云 COS 存储适配器,以地域、存储桶和对象键生成访问地址。 + *

客户端采用无同步的延迟初始化,首次并发调用可能重复创建客户端; + * 上传与删除异常仅记录日志,不提供本地路径和目录枚举。 */ public class TencentStorage implements Storage { @@ -66,9 +68,9 @@ public void setBucketName(String bucketName) { private COSClient getCOSClient() { if (cosClient == null) { - // 1 初始化用户身份信息(secretId, secretKey) + // 使用部署凭据创建客户端,避免每次文件操作都重复初始化。 COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); - // 2 设置bucket的区域, COS地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + // 地域必须与目标存储桶一致,否则请求无法定位正确的桶。 ClientConfig clientConfig = new ClientConfig(new Region(region)); cosClient = new COSClient(cred, clientConfig); } @@ -80,10 +82,18 @@ private String getBaseUrl() { return "https://" + bucketName + ".cos." + region + ".myqcloud.com/"; } + /** + * 保存文件内容,以对象键而非用户原始文件名定位对象。 + *

当前实现捕获上传异常并记录日志,正常返回不能作为上传成功凭据。 + * @param inputStream 文件内容输入流 + * @param contentLength 文件字节数 + * @param contentType 文件 MIME 类型 + * @param keyName 由调用方生成的可信对象键 + */ @Override public void store(InputStream inputStream, long contentLength, String contentType, String keyName) { try { - // 简单文件上传, 最大支持 5 GB, 适用于小文件上传, 建议 20M以下的文件使用该接口 + // 设置长度和 MIME 类型,供对象存储识别内容及下游浏览器展示。 ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(contentLength); objectMetadata.setContentType(contentType); @@ -96,16 +106,30 @@ public void store(InputStream inputStream, long contentLength, String contentTyp } } + /** + * 当前云适配器不实现目录枚举。 + * @return null + */ @Override public Stream loadAll() { return null; } + /** + * 当前云适配器不提供本地文件路径。 + * @param keyName 对象键 + * @return null + */ @Override public Path load(String keyName) { return null; } + /** + * 将对象键转换为可读取的资源,供文件下载使用。 + * @param keyName 存储对象键 + * @return 可用资源;地址无效或不可访问时可能返回 null + */ @Override public Resource loadAsResource(String keyName) { try { @@ -120,6 +144,10 @@ public Resource loadAsResource(String keyName) { return null; } + /** + * 删除底层存储对象,不负责删除数据库中的元数据记录。 + * @param keyName 待删除的对象键 + */ @Override public void delete(String keyName) { try { @@ -130,6 +158,11 @@ public void delete(String keyName) { } + /** + * 根据存储配置拼接访问地址,不检查对象存在性或生成签名凭证。 + * @param keyName 存储对象键 + * @return 对象访问地址 + */ @Override public String generateUrl(String keyName) { return getBaseUrl() + keyName; diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageAutoConfiguration.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageAutoConfiguration.java index 20ecae2b3..49a6bd211 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageAutoConfiguration.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageAutoConfiguration.java @@ -5,6 +5,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * 装配四种存储适配器,并根据 active 为统一入口选择一个实现。 + *

各工厂均声明为 Bean;选择 active 不代表其余工厂不会被 Spring 初始化。 + */ @Configuration @EnableConfigurationProperties(StorageProperties.class) public class StorageAutoConfiguration { @@ -15,6 +19,12 @@ public StorageAutoConfiguration(StorageProperties properties) { this.properties = properties; } + /** + * 按 active 选择统一存储入口的委托实现,保持业务层调用方式一致。 + * @return 已绑定存储实现的服务 + * @throws RuntimeException active 不是 local、aliyun、tencent、qiniu 时 + * @throws NullPointerException active 未配置时 + */ @Bean public StorageService storageService() { StorageService storageService = new StorageService(); @@ -35,6 +45,10 @@ public StorageService storageService() { return storageService; } + /** + * 将本地磁盘路径与访问地址配置注入对应适配器。 + * @return 完成配置的存储适配器 + */ @Bean public LocalStorage localStorage() { LocalStorage localStorage = new LocalStorage(); @@ -44,6 +58,10 @@ public LocalStorage localStorage() { return localStorage; } + /** + * 将阿里云 OSS 凭据与桶配置注入对应适配器。 + * @return 完成配置的存储适配器 + */ @Bean public AliyunStorage aliyunStorage() { AliyunStorage aliyunStorage = new AliyunStorage(); @@ -55,6 +73,10 @@ public AliyunStorage aliyunStorage() { return aliyunStorage; } + /** + * 将腾讯云 COS 凭据、桶与地域配置注入对应适配器。 + * @return 完成配置的存储适配器 + */ @Bean public TencentStorage tencentStorage() { TencentStorage tencentStorage = new TencentStorage(); @@ -66,6 +88,10 @@ public TencentStorage tencentStorage() { return tencentStorage; } + /** + * 将七牛云凭据、桶与访问域名配置注入对应适配器。 + * @return 完成配置的存储适配器 + */ @Bean public QiniuStorage qiniuStorage() { QiniuStorage qiniuStorage = new QiniuStorage(); diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageProperties.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageProperties.java index a2c8b7b77..d69a6fe43 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageProperties.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/config/StorageProperties.java @@ -2,8 +2,13 @@ import org.springframework.boot.context.properties.ConfigurationProperties; +/** + * 绑定 litemall.storage 配置,为本地及三种云存储提供初始化参数。 + *

active 选择实际使用的实现,凭据属于部署配置,不应写入源码注释。 + */ @ConfigurationProperties(prefix = "litemall.storage") public class StorageProperties { + /** 启用的存储类型:local、aliyun、tencent 或 qiniu。 */ private String active; private Local local; private Aliyun aliyun; @@ -51,7 +56,9 @@ public void setQiniu(Qiniu qiniu) { } public static class Local { + /** 本地文件对外访问前缀,需与资源映射一致并包含所需分隔符。 */ private String address; + /** 服务器磁盘保存目录,初始化时尝试创建。 */ private String storagePath; public String getAddress() { @@ -72,9 +79,13 @@ public void setStoragePath(String storagePath) { } public static class Tencent { + /** 腾讯云访问密钥标识。 */ private String secretId; + /** 云服务访问密钥,由部署配置注入。 */ private String secretKey; + /** 腾讯云存储桶所属地域。 */ private String region; + /** 目标对象存储桶名称。 */ private String bucketName; public String getSecretId() { @@ -111,9 +122,13 @@ public void setBucketName(String bucketName) { } public static class Aliyun { + /** 服务端地址或访问域名,具体格式由相应适配器使用方式决定。 */ private String endpoint; + /** 阿里云访问密钥标识。 */ private String accessKeyId; + /** 阿里云访问密钥,由部署配置注入。 */ private String accessKeySecret; + /** 目标对象存储桶名称。 */ private String bucketName; public String getEndpoint() { @@ -150,9 +165,13 @@ public void setBucketName(String bucketName) { } public static class Qiniu { + /** 服务端地址或访问域名,具体格式由相应适配器使用方式决定。 */ private String endpoint; + /** 七牛云访问密钥标识。 */ private String accessKey; + /** 云服务访问密钥,由部署配置注入。 */ private String secretKey; + /** 目标对象存储桶名称。 */ private String bucketName; public String getEndpoint() { From c2c3436b93d0142ea23686526640e82efe2cf6c1 Mon Sep 17 00:00:00 2001 From: Look4u-1 <2529702675@qq.com> Date: Fri, 16 Oct 2026 00:16:16 +0800 Subject: [PATCH 6/6] =?UTF-8?q?docs(core):=20=E7=BB=9F=E4=B8=80=E6=95=B4?= =?UTF-8?q?=E5=90=88=E6=B3=A8=E9=87=8A=E4=B8=8E=E6=BA=90=E7=A0=81=E6=8D=A2?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../litemall/core/config/CorsConfig.java | 2 +- .../core/config/ValidatorConfiguration.java | 74 +-- .../core/express/dao/ExpressInfo.java | 2 +- .../litemall/core/express/dao/Traces.java | 2 +- .../litemall/core/notify/NotifyService.java | 318 +++++------ .../litemall/core/notify/SmsSender.java | 2 +- .../litemall/core/qcode/QCodeService.java | 410 ++++++------- .../litemall/core/storage/LocalStorage.java | 2 +- .../litemall/core/storage/Storage.java | 2 +- .../core/system/SystemInistService.java | 260 ++++----- .../litemall/core/task/TaskService.java | 130 ++--- .../litemall/core/util/HttpUtil.java | 188 +++--- .../litemall/core/util/JacksonUtil.java | 538 +++++++++--------- .../core/validator/OrderValidator.java | 64 +-- .../core/validator/SortValidator.java | 64 +-- 15 files changed, 1029 insertions(+), 1029 deletions(-) diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java index 105b8813d..d785fa22c 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java @@ -9,7 +9,7 @@ /** * 跨域资源共享(CORS)配置类 * - *

小程序 / H5 前端与后端接口属于不同源(协议、域名、端口任一不同即跨域), + *

浏览器中的 H5 或管理端与后端接口属于不同源(协议、域名、端口任一不同即跨域), * 浏览器会拦截跨域请求。这里通过注册一个全局 CorsFilter,对 {@code /**} 所有接口 * 放行跨域预检与实际请求,使前端能够正常调用后端 API。

* diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java index 7c3a4fea3..b80442215 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/config/ValidatorConfiguration.java @@ -1,37 +1,37 @@ -package org.linlinjava.litemall.core.config; - -import org.hibernate.validator.HibernateValidator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; - -/** - * 参数校验器配置类 - * - *

配置 JSR-303 参数校验(@Valid / @NotBlank 等)使用 Hibernate Validator 实现, - * 并开启 fail_fast:一旦遇到第一个校验失败就立即返回,不必等全部字段校验完, - * 便于接口快速给出明确的错误提示。

- * - * @author litemall - * @since 1.0 - */ -@Configuration -public class ValidatorConfiguration { - /** - * 构造并注册全局 Validator - * - * @return fail-fast 模式的 Validator 实例 - */ - @Bean - public Validator validator() { - ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) - .configure() - .addProperty("hibernate.validator.fail_fast", "true") - .buildValidatorFactory(); - Validator validator = validatorFactory.getValidator(); - return validator; - } -} +package org.linlinjava.litemall.core.config; + +import org.hibernate.validator.HibernateValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +/** + * 参数校验器配置类 + * + *

配置 JSR-303 参数校验(@Valid / @NotBlank 等)使用 Hibernate Validator 实现, + * 并开启 fail_fast:一旦遇到第一个校验失败就立即返回,不必等全部字段校验完, + * 便于接口快速给出明确的错误提示。

+ * + * @author litemall + * @since 1.0 + */ +@Configuration +public class ValidatorConfiguration { + /** + * 构造并注册全局 Validator + * + * @return fail-fast 模式的 Validator 实例 + */ + @Bean + public Validator validator() { + ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) + .configure() + .addProperty("hibernate.validator.fail_fast", "true") + .buildValidatorFactory(); + Validator validator = validatorFactory.getValidator(); + return validator; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java index 0c880ff3b..af0c316af 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/ExpressInfo.java @@ -117,4 +117,4 @@ public String toString() { ", ShipperName='" + ShipperName + '\'' + '}'; } -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java index 99f95202b..a95d22711 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/express/dao/Traces.java @@ -35,4 +35,4 @@ public void setAcceptTime(String AcceptTime) { this.AcceptTime = AcceptTime; } -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java index 1b781c32a..0188ae80b 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java @@ -1,159 +1,159 @@ -package org.linlinjava.litemall.core.notify; - -import org.springframework.mail.MailSender; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.scheduling.annotation.Async; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * 商城通知服务类。 - * - *

统一封装邮件、短信两类通知能力,支持直发与模板发送。 - * 通过 {@link #isMailEnable()}、{@link #isSmsEnable()} 判断对应渠道是否已注入启用; - * 邮件与短信的装配在 {@code NotifyAutoConfiguration} 中完成。 - */ -public class NotifyService { - /** 邮件发送器,为 null 表示未启用邮件通知 */ - private MailSender mailSender; - /** 邮件发件人地址(来自配置 spring.mail.sendfrom) */ - private String sendFrom; - /** 邮件收件人地址(来自配置 spring.mail.sendto) */ - private String sendTo; - - /** 短信发送器,为 null 表示未启用短信通知 */ - private SmsSender smsSender; - /** 短信模板列表,元素为 {name -> templateId} 的映射 */ - private List> smsTemplate = new ArrayList<>(); - - /** 预留的微信模板配置;保留现有设置入口,当前发送流程不读取该字段。 */ - private List> wxTemplate = new ArrayList<>(); - - /** - * 邮件通知是否启用 - * - * @return true 表示已注入邮件发送器 - */ - public boolean isMailEnable() { - return mailSender != null; - } - - /** - * 短信通知是否启用 - * - * @return true 表示已注入短信发送器 - */ - public boolean isSmsEnable() { - return smsSender != null; - } - - /** - * 短信消息通知 - * - * @param phoneNumber 接收通知的电话号码 - * @param message 短消息内容,这里短消息内容必须已经在短信平台审核通过 - */ - @Async - public void notifySms(String phoneNumber, String message) { - if (smsSender == null) - return; - - smsSender.send(phoneNumber, message); - } - - /** - * 短信模版消息通知 - * - * @param phoneNumber 接收通知的电话号码 - * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID - * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 - */ - @Async - public void notifySmsTemplate(String phoneNumber, NotifyType notifyType, String[] params) { - if (smsSender == null) { - return; - } - - String templateIdStr = getTemplateId(notifyType, smsTemplate); - if (templateIdStr == null) { - return; - } - - smsSender.sendWithTemplate(phoneNumber, templateIdStr, params); - } - - /** - * 以同步的方式发送短信模版消息通知 - * - * @param phoneNumber 接收通知的电话号码 - * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID - * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 - * @return 短信发送结果;短信未启用时返回 null,模板缺失时由具体发送器处理 - */ - public SmsResult notifySmsTemplateSync(String phoneNumber, NotifyType notifyType, String[] params) { - if (smsSender == null) - return null; - - return smsSender.sendWithTemplate(phoneNumber, getTemplateId(notifyType, smsTemplate), params); - } - - /** - * 邮件消息通知, - * 接收者在spring.mail.sendto中指定 - * - * @param subject 邮件标题 - * @param content 邮件内容 - */ - @Async - public void notifyMail(String subject, String content) { - if (mailSender == null) - return; - - SimpleMailMessage message = new SimpleMailMessage(); - message.setFrom(sendFrom); - message.setTo(sendTo); - message.setSubject(subject); - message.setText(content); - mailSender.send(message); - } - - private String getTemplateId(NotifyType notifyType, List> values) { - for (Map item : values) { - String notifyTypeStr = notifyType.getType(); - - if (item.get("name").equals(notifyTypeStr)) - return item.get("templateId"); - } - return null; - } - - public void setMailSender(MailSender mailSender) { - this.mailSender = mailSender; - } - - public void setSendFrom(String sendFrom) { - this.sendFrom = sendFrom; - } - - public void setSendTo(String sendTo) { - this.sendTo = sendTo; - } - - public void setSmsSender(SmsSender smsSender) { - this.smsSender = smsSender; - } - - public void setSmsTemplate(List> smsTemplate) { - this.smsTemplate = smsTemplate; - } - - /** - * 保存预留的微信模板配置,不启用额外的消息发送渠道。 - * @param wxTemplate 微信模板配置列表 - */ - public void setWxTemplate(List> wxTemplate) { - this.wxTemplate = wxTemplate; - } -} +package org.linlinjava.litemall.core.notify; + +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.scheduling.annotation.Async; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 商城通知服务类。 + * + *

统一封装邮件、短信两类通知能力,支持直发与模板发送。 + * 通过 {@link #isMailEnable()}、{@link #isSmsEnable()} 判断对应渠道是否已注入启用; + * 邮件与短信的装配在 {@code NotifyAutoConfiguration} 中完成。 + */ +public class NotifyService { + /** 邮件发送器,为 null 表示未启用邮件通知 */ + private MailSender mailSender; + /** 邮件发件人地址(来自配置 spring.mail.sendfrom) */ + private String sendFrom; + /** 邮件收件人地址(来自配置 spring.mail.sendto) */ + private String sendTo; + + /** 短信发送器,为 null 表示未启用短信通知 */ + private SmsSender smsSender; + /** 短信模板列表,元素为 {name -> templateId} 的映射 */ + private List> smsTemplate = new ArrayList<>(); + + /** 预留的微信模板配置;保留现有设置入口,当前发送流程不读取该字段。 */ + private List> wxTemplate = new ArrayList<>(); + + /** + * 邮件通知是否启用 + * + * @return true 表示已注入邮件发送器 + */ + public boolean isMailEnable() { + return mailSender != null; + } + + /** + * 短信通知是否启用 + * + * @return true 表示已注入短信发送器 + */ + public boolean isSmsEnable() { + return smsSender != null; + } + + /** + * 短信消息通知 + * + * @param phoneNumber 接收通知的电话号码 + * @param message 短消息内容,这里短消息内容必须已经在短信平台审核通过 + */ + @Async + public void notifySms(String phoneNumber, String message) { + if (smsSender == null) + return; + + smsSender.send(phoneNumber, message); + } + + /** + * 短信模版消息通知 + * + * @param phoneNumber 接收通知的电话号码 + * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID + * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 + */ + @Async + public void notifySmsTemplate(String phoneNumber, NotifyType notifyType, String[] params) { + if (smsSender == null) { + return; + } + + String templateIdStr = getTemplateId(notifyType, smsTemplate); + if (templateIdStr == null) { + return; + } + + smsSender.sendWithTemplate(phoneNumber, templateIdStr, params); + } + + /** + * 以同步的方式发送短信模版消息通知 + * + * @param phoneNumber 接收通知的电话号码 + * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID + * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 + * @return 短信发送结果;短信未启用时返回 null,模板缺失时由具体发送器处理 + */ + public SmsResult notifySmsTemplateSync(String phoneNumber, NotifyType notifyType, String[] params) { + if (smsSender == null) + return null; + + return smsSender.sendWithTemplate(phoneNumber, getTemplateId(notifyType, smsTemplate), params); + } + + /** + * 邮件消息通知, + * 接收者在spring.mail.sendto中指定 + * + * @param subject 邮件标题 + * @param content 邮件内容 + */ + @Async + public void notifyMail(String subject, String content) { + if (mailSender == null) + return; + + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(sendFrom); + message.setTo(sendTo); + message.setSubject(subject); + message.setText(content); + mailSender.send(message); + } + + private String getTemplateId(NotifyType notifyType, List> values) { + for (Map item : values) { + String notifyTypeStr = notifyType.getType(); + + if (item.get("name").equals(notifyTypeStr)) + return item.get("templateId"); + } + return null; + } + + public void setMailSender(MailSender mailSender) { + this.mailSender = mailSender; + } + + public void setSendFrom(String sendFrom) { + this.sendFrom = sendFrom; + } + + public void setSendTo(String sendTo) { + this.sendTo = sendTo; + } + + public void setSmsSender(SmsSender smsSender) { + this.smsSender = smsSender; + } + + public void setSmsTemplate(List> smsTemplate) { + this.smsTemplate = smsTemplate; + } + + /** + * 保存预留的微信模板配置,不启用额外的消息发送渠道。 + * @param wxTemplate 微信模板配置列表 + */ + public void setWxTemplate(List> wxTemplate) { + this.wxTemplate = wxTemplate; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java index 228398bf6..b49b4f3e2 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java @@ -27,4 +27,4 @@ public interface SmsSender { * @return 短信发送结果 */ SmsResult sendWithTemplate(String phone, String templateId, String[] params); -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java index 34ab503a1..92111930a 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/qcode/QCodeService.java @@ -1,205 +1,205 @@ -package org.linlinjava.litemall.core.qcode; - -import cn.binarywang.wx.miniapp.api.WxMaService; -import me.chanjar.weixin.common.error.WxErrorException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.linlinjava.litemall.core.storage.StorageService; -import org.linlinjava.litemall.core.system.SystemConfig; -import org.linlinjava.litemall.db.domain.LitemallGroupon; -import org.linlinjava.litemall.db.domain.LitemallStorage; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; -import org.springframework.stereotype.Service; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.*; -import java.net.URL; - -/** - * 小程序二维码 / 分享图生成服务。 - * - *

生成小程序码,并将商品图片、商品名称绘制到分享图中, - * 最终保存到存储服务并返回可访问的图片 URL。 - */ -@Service -public class QCodeService { - /** 日志 */ - private final Log logger = LogFactory.getLog(QCodeService.class); - /** 微信小程序服务 */ - @Autowired - WxMaService wxMaService; - - /** 存储服务 */ - @Autowired - private StorageService storageService; - - - /** - * 创建拼团分享图 - * - * @param goodName 商品名称 - * @param goodPicUrl 商品图片地址 - * @param groupon 拼团信息,用于生成带 groupon 参数的二维码 - * @return 分享图 URL;生成失败时返回空字符串 - */ - public String createGrouponShareImage(String goodName, String goodPicUrl, LitemallGroupon groupon) { - try { - //创建该商品的二维码 - File file = wxMaService.getQrcodeService().createWxaCodeUnlimit("groupon," + groupon.getId(), "pages" + - "/index/index"); - FileInputStream inputStream = new FileInputStream(file); - //将商品图片,商品名字,商城名字画到模版图中 - byte[] imageData = drawPicture(inputStream, goodPicUrl, goodName); - ByteArrayInputStream inputStream2 = new ByteArrayInputStream(imageData); - //存储分享图 - LitemallStorage storageInfo = storageService.store(inputStream2, imageData.length, "image/jpeg", - getKeyName(groupon.getId().toString())); - - return storageInfo.getUrl(); - } catch (WxErrorException e) { - logger.error(e.getMessage(), e); - } catch (FileNotFoundException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - - return ""; - } - - - /** - * 创建商品分享图 - * - * @param goodId 商品 ID,用于生成带 goods 参数的二维码 - * @param goodPicUrl 商品图片地址 - * @param goodName 商品名称 - * @return 分享图 URL;未开启自动生成或生成失败时返回空字符串 - */ - public String createGoodShareImage(String goodId, String goodPicUrl, String goodName) { - if (!SystemConfig.isAutoCreateShareImage()) - return ""; - - try { - //创建该商品的二维码 - File file = wxMaService.getQrcodeService().createWxaCodeUnlimit("goods," + goodId, "pages/index/index"); - FileInputStream inputStream = new FileInputStream(file); - //将商品图片,商品名字,商城名字画到模版图中 - byte[] imageData = drawPicture(inputStream, goodPicUrl, goodName); - ByteArrayInputStream inputStream2 = new ByteArrayInputStream(imageData); - //存储分享图 - LitemallStorage litemallStorage = storageService.store(inputStream2, imageData.length, "image/jpeg", - getKeyName(goodId)); - - return litemallStorage.getUrl(); - } catch (WxErrorException e) { - logger.error(e.getMessage(), e); - } catch (FileNotFoundException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - - return ""; - } - - /** - * 生成分享图在存储服务中的对象键名 - * - * @param goodId 商品/拼团 ID - * @return 存储对象键名,如 GOOD_QCODE_123.jpg - */ - private String getKeyName(String goodId) { - return "GOOD_QCODE_" + goodId + ".jpg"; - } - - /** - * 将商品图片,商品名字画到模版图中 - * - * @param qrCodeImg 二维码图片 - * @param goodPicUrl 商品图片地址 - * @param goodName 商品名称 - * @return 合成的 JPG 图片字节数组 - * @throws IOException 图片读取失败时抛出 - */ - private byte[] drawPicture(InputStream qrCodeImg, String goodPicUrl, String goodName) throws IOException { - //底图 - ClassPathResource redResource = new ClassPathResource("back.png"); - BufferedImage red = ImageIO.read(redResource.getInputStream()); - - - //商品图片 - URL goodPic = new URL(goodPicUrl); - BufferedImage goodImage = ImageIO.read(goodPic); - - //小程序二维码 - BufferedImage qrCodeImage = ImageIO.read(qrCodeImg); - - // --- 画图 --- - - //底层空白 bufferedImage - BufferedImage baseImage = new BufferedImage(red.getWidth(), red.getHeight(), BufferedImage.TYPE_4BYTE_ABGR_PRE); - - //画上图片 - drawImgInImg(baseImage, red, 0, 0, red.getWidth(), red.getHeight()); - - //画上商品图片 - drawImgInImg(baseImage, goodImage, 71, 69, 660, 660); - - //画上小程序二维码 - drawImgInImg(baseImage, qrCodeImage, 448, 767, 300, 300); - - //写上商品名称 - drawTextInImg(baseImage, goodName, 65, 867); - - //转jpg - BufferedImage result = new BufferedImage(baseImage.getWidth(), baseImage - .getHeight(), BufferedImage.TYPE_3BYTE_BGR); - result.getGraphics().drawImage(baseImage, 0, 0, null); - ByteArrayOutputStream bs = new ByteArrayOutputStream(); - ImageIO.write(result, "jpg", bs); - - //最终byte数组 - return bs.toByteArray(); - } - - /** - * 在图片上绘制文本 - * - * @param baseImage 目标画布 - * @param textToWrite 要绘制的文本 - * @param x 文本左上角 x 坐标 - * @param y 文本左上角 y 坐标 - */ - private void drawTextInImg(BufferedImage baseImage, String textToWrite, int x, int y) { - Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); - g2D.setColor(new Color(167, 136, 69)); - - //TODO 注意,这里的字体必须安装在服务器上 - g2D.setFont(new Font("Microsoft YaHei", Font.PLAIN, 28)); - g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - g2D.drawString(textToWrite, x, y); - g2D.dispose(); - } - - /** - * 将一张图片绘制到目标画布的指定区域 - * - * @param baseImage 目标画布 - * @param imageToWrite 待绘制的源图片 - * @param x 绘制区域左上角 x 坐标 - * @param y 绘制区域左上角 y 坐标 - * @param width 绘制区域宽度 - * @param heigth 绘制区域高度 - */ - private void drawImgInImg(BufferedImage baseImage, BufferedImage imageToWrite, int x, int y, int width, - int heigth) { - Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); - g2D.drawImage(imageToWrite, x, y, width, heigth, null); - g2D.dispose(); - } -} +package org.linlinjava.litemall.core.qcode; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.core.storage.StorageService; +import org.linlinjava.litemall.core.system.SystemConfig; +import org.linlinjava.litemall.db.domain.LitemallGroupon; +import org.linlinjava.litemall.db.domain.LitemallStorage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.URL; + +/** + * 小程序二维码 / 分享图生成服务。 + * + *

生成小程序码,并将商品图片、商品名称绘制到分享图中, + * 最终保存到存储服务并返回可访问的图片 URL。 + */ +@Service +public class QCodeService { + /** 日志 */ + private final Log logger = LogFactory.getLog(QCodeService.class); + /** 微信小程序服务 */ + @Autowired + WxMaService wxMaService; + + /** 存储服务 */ + @Autowired + private StorageService storageService; + + + /** + * 创建拼团分享图 + * + * @param goodName 商品名称 + * @param goodPicUrl 商品图片地址 + * @param groupon 拼团信息,用于生成带 groupon 参数的二维码 + * @return 分享图 URL;生成失败时返回空字符串 + */ + public String createGrouponShareImage(String goodName, String goodPicUrl, LitemallGroupon groupon) { + try { + //创建该商品的二维码 + File file = wxMaService.getQrcodeService().createWxaCodeUnlimit("groupon," + groupon.getId(), "pages" + + "/index/index"); + FileInputStream inputStream = new FileInputStream(file); + //将商品图片,商品名字,商城名字画到模版图中 + byte[] imageData = drawPicture(inputStream, goodPicUrl, goodName); + ByteArrayInputStream inputStream2 = new ByteArrayInputStream(imageData); + //存储分享图 + LitemallStorage storageInfo = storageService.store(inputStream2, imageData.length, "image/jpeg", + getKeyName(groupon.getId().toString())); + + return storageInfo.getUrl(); + } catch (WxErrorException e) { + logger.error(e.getMessage(), e); + } catch (FileNotFoundException e) { + logger.error(e.getMessage(), e); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + + return ""; + } + + + /** + * 创建商品分享图 + * + * @param goodId 商品 ID,用于生成带 goods 参数的二维码 + * @param goodPicUrl 商品图片地址 + * @param goodName 商品名称 + * @return 分享图 URL;未开启自动生成或生成失败时返回空字符串 + */ + public String createGoodShareImage(String goodId, String goodPicUrl, String goodName) { + if (!SystemConfig.isAutoCreateShareImage()) + return ""; + + try { + //创建该商品的二维码 + File file = wxMaService.getQrcodeService().createWxaCodeUnlimit("goods," + goodId, "pages/index/index"); + FileInputStream inputStream = new FileInputStream(file); + //将商品图片,商品名字,商城名字画到模版图中 + byte[] imageData = drawPicture(inputStream, goodPicUrl, goodName); + ByteArrayInputStream inputStream2 = new ByteArrayInputStream(imageData); + //存储分享图 + LitemallStorage litemallStorage = storageService.store(inputStream2, imageData.length, "image/jpeg", + getKeyName(goodId)); + + return litemallStorage.getUrl(); + } catch (WxErrorException e) { + logger.error(e.getMessage(), e); + } catch (FileNotFoundException e) { + logger.error(e.getMessage(), e); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + + return ""; + } + + /** + * 生成分享图在存储服务中的对象键名 + * + * @param goodId 商品/拼团 ID + * @return 存储对象键名,如 GOOD_QCODE_123.jpg + */ + private String getKeyName(String goodId) { + return "GOOD_QCODE_" + goodId + ".jpg"; + } + + /** + * 将商品图片,商品名字画到模版图中 + * + * @param qrCodeImg 二维码图片 + * @param goodPicUrl 商品图片地址 + * @param goodName 商品名称 + * @return 合成的 JPG 图片字节数组 + * @throws IOException 图片读取失败时抛出 + */ + private byte[] drawPicture(InputStream qrCodeImg, String goodPicUrl, String goodName) throws IOException { + //底图 + ClassPathResource redResource = new ClassPathResource("back.png"); + BufferedImage red = ImageIO.read(redResource.getInputStream()); + + + //商品图片 + URL goodPic = new URL(goodPicUrl); + BufferedImage goodImage = ImageIO.read(goodPic); + + //小程序二维码 + BufferedImage qrCodeImage = ImageIO.read(qrCodeImg); + + // --- 画图 --- + + //底层空白 bufferedImage + BufferedImage baseImage = new BufferedImage(red.getWidth(), red.getHeight(), BufferedImage.TYPE_4BYTE_ABGR_PRE); + + //画上图片 + drawImgInImg(baseImage, red, 0, 0, red.getWidth(), red.getHeight()); + + //画上商品图片 + drawImgInImg(baseImage, goodImage, 71, 69, 660, 660); + + //画上小程序二维码 + drawImgInImg(baseImage, qrCodeImage, 448, 767, 300, 300); + + //写上商品名称 + drawTextInImg(baseImage, goodName, 65, 867); + + //转jpg + BufferedImage result = new BufferedImage(baseImage.getWidth(), baseImage + .getHeight(), BufferedImage.TYPE_3BYTE_BGR); + result.getGraphics().drawImage(baseImage, 0, 0, null); + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + ImageIO.write(result, "jpg", bs); + + //最终byte数组 + return bs.toByteArray(); + } + + /** + * 在图片上绘制文本 + * + * @param baseImage 目标画布 + * @param textToWrite 要绘制的文本 + * @param x 文本左上角 x 坐标 + * @param y 文本左上角 y 坐标 + */ + private void drawTextInImg(BufferedImage baseImage, String textToWrite, int x, int y) { + Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); + g2D.setColor(new Color(167, 136, 69)); + + //TODO 注意,这里的字体必须安装在服务器上 + g2D.setFont(new Font("Microsoft YaHei", Font.PLAIN, 28)); + g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2D.drawString(textToWrite, x, y); + g2D.dispose(); + } + + /** + * 将一张图片绘制到目标画布的指定区域 + * + * @param baseImage 目标画布 + * @param imageToWrite 待绘制的源图片 + * @param x 绘制区域左上角 x 坐标 + * @param y 绘制区域左上角 y 坐标 + * @param width 绘制区域宽度 + * @param heigth 绘制区域高度 + */ + private void drawImgInImg(BufferedImage baseImage, BufferedImage imageToWrite, int x, int y, int width, + int heigth) { + Graphics2D g2D = (Graphics2D) baseImage.getGraphics(); + g2D.drawImage(imageToWrite, x, y, width, heigth, null); + g2D.dispose(); + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java index 9c70ed803..7f295bcdb 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/LocalStorage.java @@ -142,4 +142,4 @@ public String generateUrl(String keyName) { return address + keyName; } -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java index 5d843918c..93ff59b65 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/storage/Storage.java @@ -54,4 +54,4 @@ public interface Storage { * @return 对象访问地址 */ String generateUrl(String keyName); -} \ No newline at end of file +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java index 2a4765062..0f9dbc7d6 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/SystemInistService.java @@ -1,130 +1,130 @@ -package org.linlinjava.litemall.core.system; - -import org.linlinjava.litemall.core.util.SystemInfoPrinter; -import org.linlinjava.litemall.db.service.LitemallSystemConfigService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * 系统启动初始化服务 - * - *

在当前 Bean 完成依赖注入后({@link PostConstruct})自动执行:

- *
    - *
  1. 从数据库加载全部系统配置;若某配置项缺失则用 DEFAULT_CONFIGS 兜底并回写数据库;
  2. - *
  3. 把配置整体载入 {@link SystemConfig} 的内存缓存;
  4. - *
  5. 在控制台打印一份系统信息横幅(端口、数据库、各模块开关、微信配置等),便于部署后核对。
  6. - *
- * - *

注:类名 Inist 为原项目历史拼写,未改动以免影响引用。

- * - * @author litemall - * @since 1.0 - */ -@Component -class SystemInistService { - // 保存当前实例引用(保留原项目写法),不是 Spring 自注入。 - private SystemInistService systemInistService; - - @Autowired - private Environment environment; - - /** 容器初始化入口:先保存自身引用,再载入配置、打印启动横幅 */ - @PostConstruct - private void inist() { - systemInistService = this; - initConfigs(); - SystemInfoPrinter.printInfo("Litemall 初始化信息", getSystemInfo()); - } - - /** 各项系统配置的默认值:数据库没有时以此为准 */ - private final static Map DEFAULT_CONFIGS = new HashMap<>(); - - static { - // 小程序首页各区块默认展示条数 - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_NEW, "6"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_HOT, "6"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_BRAND, "4"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_TOPIC, "4"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_CATLOG_LIST, "4"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_CATLOG_GOODS, "4"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_SHARE, "false"); - // 运费:默认 8 元,满 88 元免邮 - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_EXPRESS_FREIGHT_VALUE, "8"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_EXPRESS_FREIGHT_MIN, "88"); - // 订单:未支付 30 分钟取消,未确认 7 天自动确认,7 天自动好评 - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_UNPAID, "30"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_UNCONFIRM, "7"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_COMMENT, "7"); - // 商城信息默认值 - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_NAME, "litemall"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_ADDRESS, "上海"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_Latitude, "31.201900"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_LONGITUDE, "121.587839"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_PHONE, "021-xxxx-xxxx"); - DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_QQ, "705144434"); - } - - @Autowired - private LitemallSystemConfigService litemallSystemConfigService; - - /** 读取数据库配置,缺项用默认值补齐并回写,最后载入内存缓存 */ - private void initConfigs() { - // 1. 读取数据库全部配置信息 - Map configs = litemallSystemConfigService.queryAll(); - - // 2. 对默认配置中存在、但数据库缺失的项,补一条默认值并落库 - for (Map.Entry entry : DEFAULT_CONFIGS.entrySet()) { - if (configs.containsKey(entry.getKey())) { - continue; - } - configs.put(entry.getKey(), entry.getValue()); - litemallSystemConfigService.addConfig(entry.getKey(), entry.getValue()); - } - - SystemConfig.setConfigs(configs); - } - - /** 汇总启动时要打印的系统信息(端口、数据源、模块开关、微信配置、系统设置) */ - private Map getSystemInfo() { - Map infos = new LinkedHashMap<>(); - - infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 0, "系统信息"); - infos.put("服务器端口", environment.getProperty("server.port")); - infos.put("数据库USER", environment.getProperty("spring.datasource.druid.username")); - infos.put("数据库地址", environment.getProperty("spring.datasource.druid.url")); - infos.put("调试级别", environment.getProperty("logging.level.org.linlinjava.litemall.wx")); - - infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 1, "模块状态"); - infos.put("邮件", environment.getProperty("litemall.notify.mail.enable")); - infos.put("短信", environment.getProperty("litemall.notify.sms.enable")); - infos.put("模版消息", environment.getProperty("litemall.notify.wx.enable")); - infos.put("快递信息", environment.getProperty("litemall.express.enable")); - infos.put("快递鸟ID", environment.getProperty("litemall.express.appId")); - infos.put("对象存储", environment.getProperty("litemall.storage.active")); - infos.put("本地对象存储路径", environment.getProperty("litemall.storage.local.storagePath")); - infos.put("本地对象访问地址", environment.getProperty("litemall.storage.local.address")); - infos.put("本地对象访问端口", environment.getProperty("litemall.storage.local.port")); - - infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 2, "微信相关"); - infos.put("微信APP KEY", environment.getProperty("litemall.wx.app-id")); - infos.put("微信APP-SECRET", environment.getProperty("litemall.wx.app-secret")); - infos.put("微信支付MCH-ID", environment.getProperty("litemall.wx.mch-id")); - infos.put("微信支付MCH-KEY", environment.getProperty("litemall.wx.mch-key")); - infos.put("微信支付通知地址", environment.getProperty("litemall.wx.notify-url")); - - infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 3, "系统设置"); - infos.put("自动创建朋友圈分享图", Boolean.toString(SystemConfig.isAutoCreateShareImage())); - infos.put("商场名称", SystemConfig.getMallName()); - infos.put("首页显示记录数:NEW,HOT,BRAND,TOPIC,CatlogList,CatlogMore", - SystemConfig.getNewLimit() + "," + SystemConfig.getHotLimit() + "," + SystemConfig.getBrandLimit() + - "," + SystemConfig.getTopicLimit() + "," + SystemConfig.getCatlogListLimit() + "," + SystemConfig.getCatlogMoreLimit()); - - return infos; - } -} +package org.linlinjava.litemall.core.system; + +import org.linlinjava.litemall.core.util.SystemInfoPrinter; +import org.linlinjava.litemall.db.service.LitemallSystemConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 系统启动初始化服务 + * + *

在当前 Bean 完成依赖注入后({@link PostConstruct})自动执行:

+ *
    + *
  1. 从数据库加载全部系统配置;若某配置项缺失则用 DEFAULT_CONFIGS 兜底并回写数据库;
  2. + *
  3. 把配置整体载入 {@link SystemConfig} 的内存缓存;
  4. + *
  5. 在控制台打印一份系统信息横幅(端口、数据库、各模块开关、微信配置等),便于部署后核对。
  6. + *
+ * + *

注:类名 Inist 为原项目历史拼写,未改动以免影响引用。

+ * + * @author litemall + * @since 1.0 + */ +@Component +class SystemInistService { + // 保存当前实例引用(保留原项目写法),不是 Spring 自注入。 + private SystemInistService systemInistService; + + @Autowired + private Environment environment; + + /** 容器初始化入口:先保存自身引用,再载入配置、打印启动横幅 */ + @PostConstruct + private void inist() { + systemInistService = this; + initConfigs(); + SystemInfoPrinter.printInfo("Litemall 初始化信息", getSystemInfo()); + } + + /** 各项系统配置的默认值:数据库没有时以此为准 */ + private final static Map DEFAULT_CONFIGS = new HashMap<>(); + + static { + // 小程序首页各区块默认展示条数 + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_NEW, "6"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_HOT, "6"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_BRAND, "4"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_TOPIC, "4"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_CATLOG_LIST, "4"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_INDEX_CATLOG_GOODS, "4"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_WX_SHARE, "false"); + // 运费:默认 8 元,满 88 元免邮 + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_EXPRESS_FREIGHT_VALUE, "8"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_EXPRESS_FREIGHT_MIN, "88"); + // 订单:未支付 30 分钟取消,未确认 7 天自动确认,7 天自动好评 + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_UNPAID, "30"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_UNCONFIRM, "7"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_ORDER_COMMENT, "7"); + // 商城信息默认值 + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_NAME, "litemall"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_ADDRESS, "上海"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_Latitude, "31.201900"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_LONGITUDE, "121.587839"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_PHONE, "021-xxxx-xxxx"); + DEFAULT_CONFIGS.put(SystemConfig.LITEMALL_MALL_QQ, "705144434"); + } + + @Autowired + private LitemallSystemConfigService litemallSystemConfigService; + + /** 读取数据库配置,缺项用默认值补齐并回写,最后载入内存缓存 */ + private void initConfigs() { + // 1. 读取数据库全部配置信息 + Map configs = litemallSystemConfigService.queryAll(); + + // 2. 对默认配置中存在、但数据库缺失的项,补一条默认值并落库 + for (Map.Entry entry : DEFAULT_CONFIGS.entrySet()) { + if (configs.containsKey(entry.getKey())) { + continue; + } + configs.put(entry.getKey(), entry.getValue()); + litemallSystemConfigService.addConfig(entry.getKey(), entry.getValue()); + } + + SystemConfig.setConfigs(configs); + } + + /** 汇总启动时要打印的系统信息(端口、数据源、模块开关、微信配置、系统设置) */ + private Map getSystemInfo() { + Map infos = new LinkedHashMap<>(); + + infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 0, "系统信息"); + infos.put("服务器端口", environment.getProperty("server.port")); + infos.put("数据库USER", environment.getProperty("spring.datasource.druid.username")); + infos.put("数据库地址", environment.getProperty("spring.datasource.druid.url")); + infos.put("调试级别", environment.getProperty("logging.level.org.linlinjava.litemall.wx")); + + infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 1, "模块状态"); + infos.put("邮件", environment.getProperty("litemall.notify.mail.enable")); + infos.put("短信", environment.getProperty("litemall.notify.sms.enable")); + infos.put("模版消息", environment.getProperty("litemall.notify.wx.enable")); + infos.put("快递信息", environment.getProperty("litemall.express.enable")); + infos.put("快递鸟ID", environment.getProperty("litemall.express.appId")); + infos.put("对象存储", environment.getProperty("litemall.storage.active")); + infos.put("本地对象存储路径", environment.getProperty("litemall.storage.local.storagePath")); + infos.put("本地对象访问地址", environment.getProperty("litemall.storage.local.address")); + infos.put("本地对象访问端口", environment.getProperty("litemall.storage.local.port")); + + infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 2, "微信相关"); + infos.put("微信APP KEY", environment.getProperty("litemall.wx.app-id")); + infos.put("微信APP-SECRET", environment.getProperty("litemall.wx.app-secret")); + infos.put("微信支付MCH-ID", environment.getProperty("litemall.wx.mch-id")); + infos.put("微信支付MCH-KEY", environment.getProperty("litemall.wx.mch-key")); + infos.put("微信支付通知地址", environment.getProperty("litemall.wx.notify-url")); + + infos.put(SystemInfoPrinter.CREATE_PART_COPPER + 3, "系统设置"); + infos.put("自动创建朋友圈分享图", Boolean.toString(SystemConfig.isAutoCreateShareImage())); + infos.put("商场名称", SystemConfig.getMallName()); + infos.put("首页显示记录数:NEW,HOT,BRAND,TOPIC,CatlogList,CatlogMore", + SystemConfig.getNewLimit() + "," + SystemConfig.getHotLimit() + "," + SystemConfig.getBrandLimit() + + "," + SystemConfig.getTopicLimit() + "," + SystemConfig.getCatlogListLimit() + "," + SystemConfig.getCatlogMoreLimit()); + + return infos; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java index bd30ea650..70c315ec2 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/task/TaskService.java @@ -1,65 +1,65 @@ -package org.linlinjava.litemall.core.task; - -import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Executors; - -/** - * 延迟任务调度服务。 - * - *

内部使用 {@link DelayQueue} 保存待执行的延迟任务,并在启动时启动一个常驻线程 - * 消费队列中的到期任务;对外提供任务的添加与移除能力。 - */ -@Component -public class TaskService { - - /** 延迟任务队列,任务按到期时间排序 */ - private DelayQueue delayQueue = new DelayQueue(); - - /** - * 启动任务消费线程。 - * - *

单线程常驻消费 {@link DelayQueue},任务到期即执行;循环仅捕获 InterruptedException;任务抛出未捕获的运行时异常可能终止消费。 - */ - @PostConstruct - private void init() { - - - Executors.newSingleThreadExecutor().execute(new Runnable() { - @Override - public void run() { - while (true) { - try { - Task task = delayQueue.take(); - task.run(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - }); - } - - /** - * 添加延迟任务,已存在(按 id 相等)的任务不重复添加 - * - * @param task 待添加的延迟任务 - */ - public void addTask(Task task){ - if(delayQueue.contains(task)){ - return; - } - delayQueue.add(task); - } - - /** - * 移除延迟任务 - * - * @param task 待移除的延迟任务 - */ - public void removeTask(Task task){ - delayQueue.remove(task); - } - -} +package org.linlinjava.litemall.core.task; + +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Executors; + +/** + * 延迟任务调度服务。 + * + *

内部使用 {@link DelayQueue} 保存待执行的延迟任务,并在启动时启动一个常驻线程 + * 消费队列中的到期任务;对外提供任务的添加与移除能力。 + */ +@Component +public class TaskService { + + /** 延迟任务队列,任务按到期时间排序 */ + private DelayQueue delayQueue = new DelayQueue(); + + /** + * 启动任务消费线程。 + * + *

单线程常驻消费 {@link DelayQueue},任务到期即执行;循环仅捕获 InterruptedException;任务抛出未捕获的运行时异常可能终止消费。 + */ + @PostConstruct + private void init() { + + + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + while (true) { + try { + Task task = delayQueue.take(); + task.run(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }); + } + + /** + * 添加延迟任务,已存在(按 id 相等)的任务不重复添加 + * + * @param task 待添加的延迟任务 + */ + public void addTask(Task task){ + if(delayQueue.contains(task)){ + return; + } + delayQueue.add(task); + } + + /** + * 移除延迟任务 + * + * @param task 待移除的延迟任务 + */ + public void removeTask(Task task){ + delayQueue.remove(task); + } + +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java index 5d3290a6e..e0e5c1205 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/HttpUtil.java @@ -1,94 +1,94 @@ -package org.linlinjava.litemall.core.util; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Map; - - -/** - * HTTP 工具类,用于向指定 URL 发送 POST 请求。 - */ - -public class HttpUtil { - - private static final Log logger = LogFactory.getLog(HttpUtil.class); - - /** - * 向指定 URL 发送POST方法的请求 - * - * @param url 发送请求的 URL - * @param params 请求的参数集合 - * @return 远程资源的响应结果 - */ - @SuppressWarnings("unused") - public static String sendPost(String url, Map params) { - OutputStreamWriter out = null; - BufferedReader in = null; - StringBuilder result = new StringBuilder(); - try { - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - /*发送POST请求必须设置如下两行*/ - conn.setDoOutput(true); - conn.setDoInput(true); - /* POST方法 */ - conn.setRequestMethod("POST"); - /* 设置通用的请求属性 */ - conn.setRequestProperty("accept", "*/*"); - conn.setRequestProperty("connection", "Keep-Alive"); - conn.setRequestProperty("user-agent", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); - conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - conn.connect(); - /* 获取URLConnection对象对应的输出流 */ - out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8"); - /* 发送请求参数 */ - if (params != null) { - StringBuilder param = new StringBuilder(); - for (Map.Entry entry : params.entrySet()) { - if (param.length() > 0) { - param.append("&"); - } - param.append(entry.getKey()); - param.append("="); - param.append(entry.getValue()); - - } - - out.write(param.toString()); - } - /* flush输出流的缓冲 */ - out.flush(); - /* 定义BufferedReader输入流来读取URL的响应 */ - in = new BufferedReader( - new InputStreamReader(conn.getInputStream(), "UTF-8")); - String line; - while ((line = in.readLine()) != null) { - result.append(line); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - /* 使用finally块来关闭输出流、输入流 */ - finally { - try { - if (out != null) { - out.close(); - } - if (in != null) { - in.close(); - } - } catch (IOException ex) { - logger.error(ex.getMessage(), ex); - } - } - return result.toString(); - } -} +package org.linlinjava.litemall.core.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + + +/** + * HTTP 工具类,用于向指定 URL 发送 POST 请求。 + */ + +public class HttpUtil { + + private static final Log logger = LogFactory.getLog(HttpUtil.class); + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param params 请求的参数集合 + * @return 远程资源的响应结果 + */ + @SuppressWarnings("unused") + public static String sendPost(String url, Map params) { + OutputStreamWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + URL realUrl = new URL(url); + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + /*发送POST请求必须设置如下两行*/ + conn.setDoOutput(true); + conn.setDoInput(true); + /* POST方法 */ + conn.setRequestMethod("POST"); + /* 设置通用的请求属性 */ + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.connect(); + /* 获取URLConnection对象对应的输出流 */ + out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8"); + /* 发送请求参数 */ + if (params != null) { + StringBuilder param = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (param.length() > 0) { + param.append("&"); + } + param.append(entry.getKey()); + param.append("="); + param.append(entry.getValue()); + + } + + out.write(param.toString()); + } + /* flush输出流的缓冲 */ + out.flush(); + /* 定义BufferedReader输入流来读取URL的响应 */ + in = new BufferedReader( + new InputStreamReader(conn.getInputStream(), "UTF-8")); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + /* 使用finally块来关闭输出流、输入流 */ + finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + logger.error(ex.getMessage(), ex); + } + } + return result.toString(); + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java index b314f180b..2123ea228 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/util/JacksonUtil.java @@ -1,269 +1,269 @@ -package org.linlinjava.litemall.core.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -/** - * JSON 解析与序列化工具类,基于 Jackson 实现。 - */ -public class JacksonUtil { - - private static final Log logger = LogFactory.getLog(JacksonUtil.class); - - /** - * 解析 JSON 字符串中指定字段的字符串值。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的字符串值,不存在或解析失败时返回 null - */ - public static String parseString(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - /* 获取指定字段对应的节点 */ - JsonNode leaf = node.get(field); - if (leaf != null) - return leaf.asText(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段的字符串列表。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的字符串列表,不存在或解析失败时返回 null - */ - public static List parseStringList(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - JsonNode leaf = node.get(field); - - if (leaf != null) - /* 将字段节点转换为字符串列表 */ - return mapper.convertValue(leaf, new TypeReference>() { - }); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段的整数值。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的整数值,不存在或解析失败时返回 null - */ - public static Integer parseInteger(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - /* 获取指定字段对应的节点 */ - JsonNode leaf = node.get(field); - if (leaf != null) - return leaf.asInt(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段的整数列表。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的整数列表,不存在或解析失败时返回 null - */ - public static List parseIntegerList(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - JsonNode leaf = node.get(field); - - if (leaf != null) - /* 将字段节点转换为整数列表 */ - return mapper.convertValue(leaf, new TypeReference>() { - }); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段的布尔值。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的布尔值,不存在或解析失败时返回 null - */ - public static Boolean parseBoolean(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - /* 获取指定字段对应的节点 */ - JsonNode leaf = node.get(field); - if (leaf != null) - return leaf.asBoolean(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段的短整数值。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的短整数值,不存在或解析失败时返回 null - */ - public static Short parseShort(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - JsonNode leaf = node.get(field); - if (leaf != null) { - /* 先按 int 取值再强转为 short */ - Integer value = leaf.asInt(); - return value.shortValue(); - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段的字节值。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @return 字段对应的字节值,不存在或解析失败时返回 null - */ - public static Byte parseByte(String body, String field) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - JsonNode leaf = node.get(field); - if (leaf != null) { - /* 先按 int 取值再强转为 byte */ - Integer value = leaf.asInt(); - return value.byteValue(); - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 解析 JSON 字符串中指定字段为指定类型的对象。 - * - * @param body JSON 字符串 - * @param field 字段名 - * @param clazz 目标类型 - * @param 目标类型泛型 - * @return 字段对应的对象,不存在或解析失败时返回 null - */ - public static T parseObject(String body, String field, Class clazz) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node; - try { - /* 将 JSON 字符串解析为树形结构 */ - node = mapper.readTree(body); - node = node.get(field); - /* 将字段节点转换为指定类型的对象 */ - return mapper.treeToValue(node, clazz); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 将 JSON 字符串解析为 JSON 节点。 - * - * @param json JSON 字符串 - * @return JSON 节点,解析失败时返回 null - */ - public static Object toNode(String json) { - if (json == null) { - return null; - } - ObjectMapper mapper = new ObjectMapper(); - try { - /* 解析 JSON 字符串为 JSON 节点 */ - return mapper.readTree(json); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - - return null; - } - - /** - * 将 JSON 字符串解析为 Map。 - * - * @param data JSON 字符串 - * @return Map 对象,解析失败时返回 null - */ - public static Map toMap(String data) { - ObjectMapper objectMapper = new ObjectMapper(); - try { - /* 将 JSON 字符串反序列化为 Map */ - return objectMapper.readValue(data, new TypeReference>() { - }); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * 将对象序列化为 JSON 字符串。 - * - * @param data 待序列化的对象 - * @return JSON 字符串,序列化失败时返回 null - */ - public static String toJson(Object data) { - ObjectMapper objectMapper = new ObjectMapper(); - try { - /* 将对象序列化为 JSON 字符串 */ - return objectMapper.writeValueAsString(data); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - return null; - } -} +package org.linlinjava.litemall.core.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * JSON 解析与序列化工具类,基于 Jackson 实现。 + */ +public class JacksonUtil { + + private static final Log logger = LogFactory.getLog(JacksonUtil.class); + + /** + * 解析 JSON 字符串中指定字段的字符串值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的字符串值,不存在或解析失败时返回 null + */ + public static String parseString(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + /* 获取指定字段对应的节点 */ + JsonNode leaf = node.get(field); + if (leaf != null) + return leaf.asText(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段的字符串列表。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的字符串列表,不存在或解析失败时返回 null + */ + public static List parseStringList(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + JsonNode leaf = node.get(field); + + if (leaf != null) + /* 将字段节点转换为字符串列表 */ + return mapper.convertValue(leaf, new TypeReference>() { + }); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段的整数值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的整数值,不存在或解析失败时返回 null + */ + public static Integer parseInteger(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + /* 获取指定字段对应的节点 */ + JsonNode leaf = node.get(field); + if (leaf != null) + return leaf.asInt(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段的整数列表。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的整数列表,不存在或解析失败时返回 null + */ + public static List parseIntegerList(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + JsonNode leaf = node.get(field); + + if (leaf != null) + /* 将字段节点转换为整数列表 */ + return mapper.convertValue(leaf, new TypeReference>() { + }); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段的布尔值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的布尔值,不存在或解析失败时返回 null + */ + public static Boolean parseBoolean(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + /* 获取指定字段对应的节点 */ + JsonNode leaf = node.get(field); + if (leaf != null) + return leaf.asBoolean(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段的短整数值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的短整数值,不存在或解析失败时返回 null + */ + public static Short parseShort(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + JsonNode leaf = node.get(field); + if (leaf != null) { + /* 先按 int 取值再强转为 short */ + Integer value = leaf.asInt(); + return value.shortValue(); + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段的字节值。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @return 字段对应的字节值,不存在或解析失败时返回 null + */ + public static Byte parseByte(String body, String field) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + JsonNode leaf = node.get(field); + if (leaf != null) { + /* 先按 int 取值再强转为 byte */ + Integer value = leaf.asInt(); + return value.byteValue(); + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 解析 JSON 字符串中指定字段为指定类型的对象。 + * + * @param body JSON 字符串 + * @param field 字段名 + * @param clazz 目标类型 + * @param 目标类型泛型 + * @return 字段对应的对象,不存在或解析失败时返回 null + */ + public static T parseObject(String body, String field, Class clazz) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + try { + /* 将 JSON 字符串解析为树形结构 */ + node = mapper.readTree(body); + node = node.get(field); + /* 将字段节点转换为指定类型的对象 */ + return mapper.treeToValue(node, clazz); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 将 JSON 字符串解析为 JSON 节点。 + * + * @param json JSON 字符串 + * @return JSON 节点,解析失败时返回 null + */ + public static Object toNode(String json) { + if (json == null) { + return null; + } + ObjectMapper mapper = new ObjectMapper(); + try { + /* 解析 JSON 字符串为 JSON 节点 */ + return mapper.readTree(json); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + + return null; + } + + /** + * 将 JSON 字符串解析为 Map。 + * + * @param data JSON 字符串 + * @return Map 对象,解析失败时返回 null + */ + public static Map toMap(String data) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + /* 将 JSON 字符串反序列化为 Map */ + return objectMapper.readValue(data, new TypeReference>() { + }); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + /** + * 将对象序列化为 JSON 字符串。 + * + * @param data 待序列化的对象 + * @return JSON 字符串,序列化失败时返回 null + */ + public static String toJson(Object data) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + /* 将对象序列化为 JSON 字符串 */ + return objectMapper.writeValueAsString(data); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java index 57883484c..3dd97e78f 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/OrderValidator.java @@ -1,32 +1,32 @@ -package org.linlinjava.litemall.core.validator; - -import com.google.common.collect.Lists; - -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.List; - -/** - * {@link Order} 注解的校验器,校验排序方式是否在允许范围内。 - */ -public class OrderValidator implements ConstraintValidator { - private List valueList; - - @Override - public void initialize(Order order) { - valueList = Lists.newArrayList(); - /* 将允许值统一转为大写,便于校验时忽略大小写 */ - for (String val : order.accepts()) { - valueList.add(val.toUpperCase()); - } - } - - @Override - public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { - /* 校验时忽略大小写 */ - if (!valueList.contains(s.toUpperCase())) { - return false; - } - return true; - } -} +package org.linlinjava.litemall.core.validator; + +import com.google.common.collect.Lists; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * {@link Order} 注解的校验器,校验排序方式是否在允许范围内。 + */ +public class OrderValidator implements ConstraintValidator { + private List valueList; + + @Override + public void initialize(Order order) { + valueList = Lists.newArrayList(); + /* 将允许值统一转为大写,便于校验时忽略大小写 */ + for (String val : order.accepts()) { + valueList.add(val.toUpperCase()); + } + } + + @Override + public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { + /* 校验时忽略大小写 */ + if (!valueList.contains(s.toUpperCase())) { + return false; + } + return true; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java index d775e28bb..7fdd98b91 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/validator/SortValidator.java @@ -1,32 +1,32 @@ -package org.linlinjava.litemall.core.validator; - -import com.google.common.collect.Lists; - -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.List; - -/** - * {@link Sort} 注解的校验器,校验排序字段是否在允许范围内。 - */ -public class SortValidator implements ConstraintValidator { - private List valueList; - - @Override - public void initialize(Sort sort) { - valueList = Lists.newArrayList(); - /* 将允许值统一转为大写,便于校验时忽略大小写 */ - for (String val : sort.accepts()) { - valueList.add(val.toUpperCase()); - } - } - - @Override - public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { - /* 校验时忽略大小写 */ - if (!valueList.contains(s.toUpperCase())) { - return false; - } - return true; - } -} +package org.linlinjava.litemall.core.validator; + +import com.google.common.collect.Lists; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * {@link Sort} 注解的校验器,校验排序字段是否在允许范围内。 + */ +public class SortValidator implements ConstraintValidator { + private List valueList; + + @Override + public void initialize(Sort sort) { + valueList = Lists.newArrayList(); + /* 将允许值统一转为大写,便于校验时忽略大小写 */ + for (String val : sort.accepts()) { + valueList.add(val.toUpperCase()); + } + } + + @Override + public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { + /* 校验时忽略大小写 */ + if (!valueList.contains(s.toUpperCase())) { + return false; + } + return true; + } +}