华为应用内支付完成会员订购,会员与商品调整

This commit is contained in:
akun 2024-04-18 15:52:07 +08:00
parent 3e251b4c48
commit 9400c9985c
33 changed files with 1548 additions and 964 deletions

11
pom.xml
View File

@ -174,6 +174,17 @@
<version>${sf.version}</version>
</dependency>
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-payment</artifactId>
<version>${sf.version}</version>
</dependency>
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-service</artifactId>
<version>${sf.version}</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.smarterFramework</groupId>

View File

@ -71,7 +71,15 @@
<artifactId>sf-order</artifactId>
</dependency>
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-payment</artifactId>
</dependency>
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-service</artifactId>
</dependency>
</dependencies>

View File

@ -18,7 +18,7 @@ sf:
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 7781
port: 80
servlet:
# 应用的访问路径
context-path: /

View File

@ -3,10 +3,8 @@ package com.sf.order.controller;
import com.sf.common.annotation.Log;
import com.sf.common.core.controller.BaseController;
import com.sf.common.core.domain.AjaxResult;
import com.sf.common.core.domain.entity.SysUser;
import com.sf.common.core.page.TableDataInfo;
import com.sf.common.enums.BusinessType;
import com.sf.common.utils.SecurityUtils;
import com.sf.common.utils.poi.ExcelUtil;
import com.sf.order.domain.OrderInfo;
import com.sf.order.domain.dto.OrderCreateDto;

View File

@ -3,6 +3,7 @@ package com.sf.order.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.sf.common.annotation.Excel;
import com.sf.common.core.domain.BaseEntity;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -14,6 +15,7 @@ import java.util.Date;
* @author ztzh
* @date 2024-04-09
*/
@Data
public class OrderInfo extends BaseEntity {
private static final long serialVersionUID = 1L;
@ -26,7 +28,7 @@ public class OrderInfo extends BaseEntity {
* 订单编号
*/
@Excel(name = "订单编号")
private String orderNo;
private Long orderNo;
/**
* 订单状态: 0:待支付 1:已付款待发货 2:配送中 3:待取货 4:支付超时系统结束 5:客户自主取消 6:已完成
@ -155,209 +157,27 @@ public class OrderInfo extends BaseEntity {
@Excel(name = "减免金额(优惠券抵扣)")
private Long reductionAmout;
public void setId(Long id) {
this.id = id;
}
/**
* * 商品数量
*/
private Integer goodsCount;
/**
* * 商品类型
* * 0消耗型商品
* * 1非消耗型商品
* * 2自动续期订阅商品
*/
private Integer goodsType;
public Long getId() {
return id;
}
/**
* 商品单价
*/
private Long goodsPrice;
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
/**
* 商品编号
*/
private String goodsCode;
public String getOrderNo() {
return orderNo;
}
public void setOrderStatus(Long orderStatus) {
this.orderStatus = orderStatus;
}
public Long getOrderStatus() {
return orderStatus;
}
public void setPayType(Long payType) {
this.payType = payType;
}
public Long getPayType() {
return payType;
}
public void setPayChannel(Long payChannel) {
this.payChannel = payChannel;
}
public Long getPayChannel() {
return payChannel;
}
public void setOrderAmt(Long orderAmt) {
this.orderAmt = orderAmt;
}
public Long getOrderAmt() {
return orderAmt;
}
public void setFreightAmt(Long freightAmt) {
this.freightAmt = freightAmt;
}
public Long getFreightAmt() {
return freightAmt;
}
public void setPayAmt(Long payAmt) {
this.payAmt = payAmt;
}
public Long getPayAmt() {
return payAmt;
}
public void setReallyAmt(Long reallyAmt) {
this.reallyAmt = reallyAmt;
}
public Long getReallyAmt() {
return reallyAmt;
}
public void setReceiveType(Long receiveType) {
this.receiveType = receiveType;
}
public Long getReceiveType() {
return receiveType;
}
public void setGoodsId(Long goodsId) {
this.goodsId = goodsId;
}
public Long getGoodsId() {
return goodsId;
}
public void setBusinessId(Long businessId) {
this.businessId = businessId;
}
public Long getBusinessId() {
return businessId;
}
public void setReceiveAddrId(Long receiveAddrId) {
this.receiveAddrId = receiveAddrId;
}
public Long getReceiveAddrId() {
return receiveAddrId;
}
public void setPayTime(Date payTime) {
this.payTime = payTime;
}
public Date getPayTime() {
return payTime;
}
public void setCreateUserId(Long createUserId) {
this.createUserId = createUserId;
}
public Long getCreateUserId() {
return createUserId;
}
public void setUpdateUserId(Long updateUserId) {
this.updateUserId = updateUserId;
}
public Long getUpdateUserId() {
return updateUserId;
}
public void setIsDelete(Long isDelete) {
this.isDelete = isDelete;
}
public Long getIsDelete() {
return isDelete;
}
public void setTrackNo(String trackNo) {
this.trackNo = trackNo;
}
public String getTrackNo() {
return trackNo;
}
public void setOrderType(Long orderType) {
this.orderType = orderType;
}
public Long getOrderType() {
return orderType;
}
public void setOutOrderNo(String outOrderNo) {
this.outOrderNo = outOrderNo;
}
public String getOutOrderNo() {
return outOrderNo;
}
public void setPayData(String payData) {
this.payData = payData;
}
public String getPayData() {
return payData;
}
public void setReductionAmout(Long reductionAmout) {
this.reductionAmout = reductionAmout;
}
public Long getReductionAmout() {
return reductionAmout;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("orderNo", getOrderNo())
.append("orderStatus", getOrderStatus())
.append("payType", getPayType())
.append("payChannel", getPayChannel())
.append("orderAmt", getOrderAmt())
.append("freightAmt", getFreightAmt())
.append("payAmt", getPayAmt())
.append("reallyAmt", getReallyAmt())
.append("receiveType", getReceiveType())
.append("goodsId", getGoodsId())
.append("businessId", getBusinessId())
.append("receiveAddrId", getReceiveAddrId())
.append("createTime", getCreateTime())
.append("payTime", getPayTime())
.append("createUserId", getCreateUserId())
.append("updateUserId", getUpdateUserId())
.append("isDelete", getIsDelete())
.append("updateTime", getUpdateTime())
.append("trackNo", getTrackNo())
.append("orderType", getOrderType())
.append("outOrderNo", getOutOrderNo())
.append("payData", getPayData())
.append("reductionAmout", getReductionAmout())
.toString();
}
}

View File

@ -71,5 +71,23 @@ public class OrderListResVo {
*/
private Integer goodsCount;
/**
* 商品价格
*/
private Long goodsPrice;
/**
* * 商品类型
* * 0消耗型商品
* * 1非消耗型商品
* * 2自动续期订阅商品
*/
private Integer goodsType;
/**
* 商品编码
*/
private String goodsCode;
}

View File

@ -72,4 +72,7 @@ public interface IOrderInfoService
void orderPay(Long orderId);
OrderInfo selectOrderInfoByOrderNo(String orderNo);
void insertOrder(OrderInfo orderInfo);
}

View File

@ -62,7 +62,7 @@ public class OrderInfoServiceImpl implements IOrderInfoService {
@Override
public Long createOrder(OrderCreateDto orderCreateDto) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderNo(snowflakeIdWorker.nextId() + "");
orderInfo.setOrderNo(snowflakeIdWorker.nextId());
orderInfo.setPayType(1L);
orderInfo.setReceiveType(0L);
orderInfo.setOrderStatus(0L);
@ -113,6 +113,11 @@ public class OrderInfoServiceImpl implements IOrderInfoService {
return orderInfoMapper.selectOrderInfoByOrderNo(orderNo);
}
@Override
public void insertOrder(OrderInfo orderInfo) {
orderInfoMapper.insertOrderInfo(orderInfo);
}
/**
* 批量删除订单基础信息
*

View File

@ -42,13 +42,16 @@
<result property="goodsSpec" column="goods_spec" />
<result property="goodsCount" column="goods_count" />
<result property="subscriptionCancellationTime" column="subscription_cancellation_time" />
<result property="goodsType" column="goods_type" />
<result property="goodsPrice" column="goods_price" />
<result property="goodsCode" column="goods_code" />
</resultMap>
<sql id="selectOrderInfoVo">
select id, order_no, order_status, pay_type, pay_channel, order_amt, freight_amt, pay_amt, really_amt, receive_type, goods_id, business_id, receive_addr_id, create_time, pay_time, create_user_id, update_user_id, is_delete, update_time, track_no, order_type, out_order_no, pay_data, reduction_amout from Order_info
</sql>
<sql id="OrderListInfoVo">
SELECT a.id,a.order_no,a.order_status,a.order_amt,a.pay_time,a.count as goods_count,a.subscription_cancellation_time,b.product_title,b.product_picture,b.product_desc,b.goods_spec
SELECT a.id,a.order_no,a.order_status,a.order_amt,a.pay_time,a.goods_count,a.subscription_cancellation_time,a.goods_type,a.goods_price,a.goods_code,b.product_title,b.product_picture,b.product_desc,b.goods_spec
FROM Order_info a LEFT JOIN GOODS_MESSAGES b ON a.goods_id = b.id
</sql>
@ -124,6 +127,10 @@
<if test="outOrderNo != null">out_order_no,</if>
<if test="payData != null">pay_data,</if>
<if test="reductionAmout != null">reduction_amout,</if>
<if test="goodsCount != null">goods_count,</if>
<if test="goodsType != null">goods_type,</if>
<if test="goodsPrice != null">goods_price,</if>
<if test="goodsCode != null">goods_code,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
@ -150,6 +157,10 @@
<if test="outOrderNo != null">#{outOrderNo},</if>
<if test="payData != null">#{payData},</if>
<if test="reductionAmout != null">#{reductionAmout},</if>
<if test="goodsCount != null">#{goodsCount},</if>
<if test="goodsType != null">#{goodsType},</if>
<if test="goodsPrice != null">#{goodsPrice},</if>
<if test="goodsCode != null">#{goodsCode},</if>
</trim>
</insert>

View File

@ -30,11 +30,20 @@
<groupId>com.smarterFramework</groupId>
<artifactId>sf-order</artifactId>
</dependency>
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-service</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.73</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>

View File

@ -16,7 +16,7 @@ import org.springframework.context.annotation.Configuration;
public class HuaweiPaymentConfig {
/**
* 客户端id对应各平台的appKey
* 客户端idoauth用
*/
@Value("${huawei.payment.clientId:110693217}")
private String clientId;
@ -27,5 +27,11 @@ public class HuaweiPaymentConfig {
@Value("${huawei.payment.clientSecret:1410c01bc71c7ba587175ae79e500137c70945acc1416a38127cf98a09a6f8ba}")
private String clientSecret;
/**
* 应用id
*/
@Value("${huawei.payment.appId:5765880207854169373}")
private String appId;
}

View File

@ -0,0 +1,29 @@
package com.sf.payment.constant;
import io.jsonwebtoken.Claims;
/**
* 商品信息
*
* @author zoukun
*/
public class GoodsConstants {
/**
* 商品类型
* 0消耗型商品
*/
public static final Integer GOODS_TYPE_CONSUMABLE = 0;
/**
* 商品类型
* 1非消耗型商品
*/
public static final Integer GOODS_TYPE_NON_CONSUMABLE = 1;
/**
* 商品类型
* 2自动续期订阅商品
*/
public static final Integer GOODS_TYPE_AUTOMATIC_RENEWAL_SUBSCRIPTION = 2;
}

View File

@ -2,11 +2,13 @@ package com.sf.payment.controller;
import com.alibaba.fastjson2.JSONObject;
import com.sf.common.core.domain.AjaxResult;
import com.sf.payment.constant.GoodsConstants;
import com.sf.payment.domain.HuaweiPaymentCallback;
import com.sf.payment.domain.HuaweiPurchasesVerifyDTO;
import com.sf.payment.service.IHuaweiPaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -36,7 +38,7 @@ public class HuaweiPaymentController {
/**
* 华为购买验证
*/
@RequestMapping("/huawei/purchases/verify")
@PostMapping("/huawei/purchases/verify")
public AjaxResult purchasesVerify(@Validated @RequestBody HuaweiPurchasesVerifyDTO verifyDTO) {
log.info("进入/huawei/purchases/tokens/verify params" + JSONObject.toJSONString(verifyDTO));
huaweiPaymentService.purchasesVerify(verifyDTO);

View File

@ -1,42 +0,0 @@
package com.sf.payment.domain;
import lombok.*;
import java.io.Serializable;
/**
* 授权所需的token
*
* @author zoukun
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthToken implements Serializable {
private String accessToken;
private int expireIn;
private String refreshToken;
private int refreshTokenExpireIn;
private String uid;
private String openId;
private String accessCode;
private String unionId;
/**
* 华为返回 生成的Access Token中包含的scope
*/
private String scope;
/**
* 华为返回 固定返回Bearer标识返回Access Token的类型
*/
private String tokenType;
/**
* 华为返回 返回JWT格式数据包含用户基本帐号用户邮箱等信息
* 参照https://developer.huawei.com/consumer/cn/doc/HMSCore-References/account-verify-id-token_hms_reference-0000001050050577#section3142132691914
*/
private String idToken;
}

View File

@ -1,73 +0,0 @@
package com.sf.payment.domain;
import com.alibaba.fastjson2.JSONObject;
import lombok.*;
import java.io.Serializable;
/**
* 授权成功后的用户信息根据授权平台的不同获取的数据完整性也不同
*
* @author zoukun
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthUser implements Serializable {
/**
* 用户第三方系统的唯一id
*/
private String uuid;
/**
* 用户名
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户头像
*/
private String avatar;
/**
* 用户网址
*/
private String blog;
/**
* 所在公司
*/
private String company;
/**
* 位置
*/
private String location;
/**
* 用户邮箱
*/
private String email;
/**
* 用户手机号
*/
private String mobileNumber;
/**
* 用户备注各平台中的用户个人介绍
*/
private String remark;
/**
* 用户来源
*/
private String source;
/**
* 用户授权的token信息
*/
private AuthToken token;
/**
* 第三方平台返回的原始用户信息
*/
private JSONObject rawUserInfo;
}

View File

@ -21,31 +21,33 @@ public class HuaweiPurchasesVerifyDTO implements Serializable {
/**
* 商品类别取值包括
*
* 0消耗型商品
* 1非消耗型商品
* 2订阅型商品
* 商品类型
* 0消耗型商品
* 1非消耗型商品
* 2自动续期订阅商品
*/
@NotNull
private Integer kind;
private Integer type;
/**
* 待下发商品的购买Token发起购买和查询待消费商品信息时均会返回purchaseToken参数
* 包含订单信息的JWS格式数据
* 可参见对返回结果验签
* 解码验签获取相关购买数据的JSON字符串
* 其包含的参数请参见PurchaseOrderPayload
*/
@NotBlank(message = "待下发商品的购买Token不能为空")
private String purchaseToken;
private String jwsPurchaseOrder;
/**
* 待下发商品ID商品ID来源于您在AppGallery Connect中配置商品信息时设置的商品ID
* 包含订阅状态信息的
* JWS格式数据
* 可参见对返回结果验签
* 解码验签获取相关订阅状态
* 信息的JSON字符串
* 其包含的参数请参见
* SubGroupStatusPayload
*/
@NotBlank(message = "待下发商品ID不能为空")
private String productId;
private String jwsSubscriptionStatus;
/**
* 订单号
*/
@NotBlank(message = "订单号不能为空")
private String orderNo;
}

View File

@ -0,0 +1,35 @@
package com.sf.payment.domain;
import lombok.Data;
/**
* 功能描述:
*
* @author a_kun
* @date 2024/4/17 14:41
*/
@Data
public class HuaweiQueryResponse {
/**
* 返回码
* 0成功
* 其他失败具体请参见错误码
*/
private String responseCode;
/**
* 响应描述
*/
private String responseMessage;
/**
* 包含已购订单相关状态信息的JWS格式数据可参见对返回结果验签解码验签获取相关订单状态信息的JSON字符串其包含的参数具体请参见表PurchaseOrderPayload说明
*/
private String jwsPurchaseOrder;
/**
* 包含已购订阅相关状态信息的JWS格式数据可参见对返回结果验签解码验签获取相关订阅状态信息的JSON字符串其包含的参数请参见SubGroupStatusPayload
*/
private String jwsSubGroupStatus;
}

View File

@ -0,0 +1,165 @@
package com.sf.payment.domain;
import lombok.Data;
/**
* 功能描述:
* 订单信息模型支持消耗型商品非消耗型商品和自动续期订阅商品
* @author a_kun
* @date 2024/4/16 15:29
*/
@Data
public class PurchaseOrderPayload {
/**
* 具体一笔订单中对应的购买订单号ID
*/
private String purchaseOrderId;
/**
* 购买token
* 在购买消耗型/非消耗型商品场景中与具体购买订单一一对应
* 在自动续期订阅商品场景中与订阅ID一一对应
*/
private String purchaseToken;
/**
* 应用ID
*/
private String applicationId;
/**
* 商品ID每种商品必须有唯一的ID由应用在PMS中维护或者应用发起购买时传入
* 说明
* 为避免资金损失您在对支付结果验签成功后必须对其进行校验
*/
private String productId;
/**
* 商品类型具体取值如下
* 0消耗型商品
* 1非消耗型商品
* 2自动续期订阅商品
*/
private Integer productType;
/**
* 购买时间UTC时间戳以毫秒为单位
* 如果没有完成购买则没有值
*/
private Long purchaseTime;
/**
* 发货状态具体取值如下
* 1已发货
* 2未发货
*/
private Integer finishStatus;
/**
* 价格单位
* 实际价格*100以后的值商品实际价格精确到小数点后2位例如此参数值为501则表示商品实际价格为5.01
*/
private Long price;
/**
* 用定价货币的币种请参见ISO 4217标准
* 说明
* 为避免资金损失您在对支付结果验签成功后必须对其进行校验
*/
private String currency;
/**
* 商户侧保留信息由您在调用支付接口时传入
*/
private String developerPayload;
/**
* 购买订单撤销原因
* 0其他
* 1用户遇到问题退款
*/
private Integer purchaseOrderRevocationReasonCode;
/**
* 购买订单撤销时间
* UTC时间戳以毫秒为单位
*/
private Long revocationTime;
/**
* 优惠类型
* 1推介促销
*/
private Integer offerTypeCode;
/**
* 优惠ID
*/
private String offerId;
/**
* 国家/地区码用于区分国家/地区信息请参见ISO 3166
*/
private String countryCode;
/**
* 签名时间UTC时间戳以毫秒为单位
*/
private Long signedTime;
// 以下参数只在自动续期订阅商品场景返回
/**
* 订阅连续购买段的唯一ID
* 当用户切换商品不会重置此ID
*/
private String subGroupGenerationId;
/**
* 订阅连续购买段的唯一ID
* 当用户切换订阅商品时
* 此订阅ID会发生改变
*/
private String subscriptionId;
/**
* 订阅组ID
*/
private String subGroupId;
/**
* 此次购买的有效周期
* 采用ISO 8601格式
* 例如P1W表示一周
* P1M表示一个月
*/
private String duration;
/**
* 订阅周期段类型
* 0正常周期段
* 1延期周期段
*/
private Integer durationTypeCode;
/*
ISO 8601的时间持续期限表示
在ISO 8601中时间持续期限的表示采用了一种简洁而明确的格式例如 P10D其中 P 表示周期Period后面的数字表示周期的长度而末尾的字母表示周期的单位这种表示法主要用于描述时间段的长度而不关注具体的时刻
P 表示周期Period 此字母指示接下来的时间表示将是一个时间段的描述而非具体的日期或时刻
后面的数字 这个数字表示时间段的长度可以是整数或小数它指示了在时间单位内的周期数量
末尾的字母表示周期的单位 P10D 末尾的 D 表示周期的单位是天DaysISO 8601定义了多种可能的时间单位包括
Y 表示年份例如 P2Y 表示2年的时间段
M 表示月份例如 P3M 表示3个月的时间段
W 表示周数例如 P1W 表示1周的时间段
D 表示天数例如 P10D 表示10天的时间段
T时间分隔符 如果时间段中包含了时间信息日期和时间之间用 T 分隔例如 P1DT12H 表示1天12小时的时间段
H小时M分钟S 用于表示时秒的时间段长度例如 PT2H30M 表示2小时30分钟的时间段
示例
P1Y 表示1年的时间段
P3M 表示3个月的时间段
P2W 表示2周的时间段
P4DT6H30M 表示4天6小时30分钟的时间段
*/
}

View File

@ -0,0 +1,48 @@
package com.sf.payment.domain;
import lombok.Data;
import java.util.List;
/**
* 功能描述:
* 已购订阅相关状态信息
* @author a_kun
* @date 2024/4/16 15:29
*/
@Data
public class SubGroupStatusPayload {
/**
* 应用ID
*/
private String applicationId;
/**
* 应用包名
*/
private String packageName;
/**
* 订阅组ID
*/
private String subGroupId;
/**
* 订阅组中最后生效的
* 订阅状态
* SubscriptionStatus
* 比如A切换BB切换C
* 此处是C的订阅状态
*/
private SubscriptionStatus lastSubscriptionStatus;
/**
* 订阅组最近生效的
* 历史订阅状态
* SubscriptionStatus的列表比如A切换BB切换C这里包含CBA三个订阅状态信息
*/
private List<SubscriptionStatus> historySubscriptionStatusList;
}

View File

@ -0,0 +1,91 @@
package com.sf.payment.domain;
import lombok.Data;
import java.util.List;
/**
* 功能描述:
* 当前订阅最新的未来扣费计划
* @author a_kun
* @date 2024/4/16 15:29
*/
@Data
public class SubRenewalInfo {
/**
* 订阅连续购买段的唯一ID
* 当用户切换商品不会重置此ID
*/
private String subGroupGenerationId;
/**
* 下周期生效场景下下期将续期的商品ID
*/
private String nextRenewPeriodProductId;
/**
* 当前生效的商品ID
*/
private String productId;
/**
* 自动续期状态
* 0关闭
* 1打开
*/
private Integer autoRenewStatusCode;
/**
* 系统是否还在尝试扣费
* true
* false
*/
private Boolean hasInBillingRetryPeriod;
/**
* 目前涨价状态码
* 1用户暂未同意涨价
* 2用户已同意涨价
*/
private Integer priceIncreaseStatusCode;
/**
* 优惠类型
* 1推介促销
*/
private Integer offerTypeCode;
/**
* 优惠ID
*/
private String offerId;
/**
* 下期续费价格单位取消订阅场景下不返回
*/
private String renewalPrice;
/**
* 币种
*/
private String currency;
/**
* 续期时间UTC时间戳以毫秒为单位
*/
private Long renewalTime;
/**
* 订阅续期失败的原因
* 1用户取消
* 2商品无效
* 3签约无效
* 4扣费异常
* 5用户不同意涨价
* 6未知
*/
private String expirationIntent;
}

View File

@ -0,0 +1,68 @@
package com.sf.payment.domain;
import lombok.Data;
import java.util.List;
/**
* 功能描述:
* 订阅组中最后生效的订阅状态
* @author a_kun
* @date 2024/4/16 15:29
*/
@Data
public class SubscriptionStatus {
/**
* 订阅连续购买段的唯一ID
* 当用户切换商品不会重置此ID
*/
private String subGroupGenerationId;
/**
* 订阅连续购买段的唯一ID
* 当用户切换订阅商品时
* 此订阅ID会发生改变
*/
private String subscriptionId;
/**
* 购买token
* 在购买消耗型/非消耗型商品场景中与具体购买订单一一对应
* 在自动续期订阅商品场景中与订阅ID一一对应
*/
private String purchaseToken;
/**
* 订阅状态
* 1生效状态
* 2已到期
* 3尝试扣费
* 5撤销
* 6暂停
*/
private Integer status;
/**
* 自动续期订阅商品的过期时间UTC时间戳以毫秒为单位
*/
private Long expiresTime;
/**
* 当前订阅最新的一笔购买订单包含的参数请参见PurchaseOrderPayload
*/
private PurchaseOrderPayload lastPurchaseOrder;
/**
* 当前订阅最新的购买订单列表包含续期延期折算等产生的购买订单
* 购买订单包含的参数请参见PurchaseOrderPayload
*/
private List<PurchaseOrderPayload> recentPurchaseOrderList;
/**
* 当前订阅最新的未来扣费计划包含的参数请参见SubRenewalInfo
*/
private SubRenewalInfo renewalInfo;
}

View File

@ -6,26 +6,32 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.sf.common.utils.SecurityUtils;
import com.sf.common.utils.SnowflakeIdWorker;
import com.sf.order.domain.OrderInfo;
import com.sf.order.service.IOrderInfoService;
import com.sf.payment.config.HuaweiPaymentConfig;
import com.sf.payment.domain.HuaweiPurchasesVerifyDTO;
import com.sf.payment.domain.HuaweiPurchasesVerifyResponseDTO;
import com.sf.payment.domain.InAppPurchaseData;
import com.sf.payment.constant.GoodsConstants;
import com.sf.payment.domain.*;
import com.sf.payment.service.IHuaweiPaymentService;
import com.sf.payment.utils.HuaweiTokenGenerator;
import com.sf.service.domain.GoodsMessages;
import com.sf.service.service.IGoodsMessagesService;
import com.sf.system.domain.UserMember;
import com.sf.system.service.IUserMemberService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.X509EncodedKeySpec;
@ -45,58 +51,204 @@ import java.util.Map;
@Service
public class HuaweiPaymentServiceImpl implements IHuaweiPaymentService {
// token url to get the authorization
/**
* token url to get the authorization
*/
private static final String TOKEN_URL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
private static final String VERIFY_TOKEN_URL = "https://orders-drcn.iap.cloud.huawei.com.cn/applications/purchases/tokens/verify";
/**
* 查询消耗型/非消耗型商品的订单最新状态
*/
private static final String ORDER_STATUS_QUERY_URL = "/order/harmony/v1/application/order/status/query";
/**
* 查询自动续期订阅商品的最新状态
*/
private static final String SUBSCRIPTION_STATUS_QUERY_URL = "/subscription/harmony/v1/application/subscription/status/query";
/**
* 站点信息(中国)
*/
private static final String ROOT_URL = "https://iap.cloud.huawei.com";
private static final String PUBLIC_KEY = "PUBLIC_KEY";
@Autowired
@Resource
private IOrderInfoService orderInfoService;
@Autowired
@Resource
private IUserMemberService userMemberService;
@Autowired
@Resource
private IGoodsMessagesService goodsMessagesService;
@Resource
private HuaweiPaymentConfig huaweiPaymentConfig;
@Resource
private SnowflakeIdWorker snowflakeIdWorker;
@Override
@Transactional(rollbackFor = Exception.class)
public void purchasesVerify(HuaweiPurchasesVerifyDTO verifyDTO) {
// construct the Authorization in Header
Map<String, String> headers = buildAuthorization(getAppAT(huaweiPaymentConfig.getClientId(), huaweiPaymentConfig.getClientSecret()));
// 待发放会员商品
PurchaseOrderPayload huaweiQueryResponsePurchaseOrderPayload;
// 验证 TODO 证书验签官网未实现
if (GoodsConstants.GOODS_TYPE_CONSUMABLE.equals(verifyDTO.getType())
|| GoodsConstants.GOODS_TYPE_NON_CONSUMABLE.equals(verifyDTO.getType())) {
Assert.hasText(verifyDTO.getJwsPurchaseOrder(), "订单信息不能为空");
// 消耗/非消耗商品购买验证
consumablePurchasesVerify(verifyDTO.getJwsPurchaseOrder());
} else if (GoodsConstants.GOODS_TYPE_AUTOMATIC_RENEWAL_SUBSCRIPTION.equals(verifyDTO.getType())) {
Assert.hasText(verifyDTO.getJwsSubscriptionStatus(), "订单信息不能为空");
// 订阅商品购买验证
subscriptionPurchasesVerify(verifyDTO.getJwsSubscriptionStatus());
} else {
throw new IllegalArgumentException("商品类型错误!");
}
}
private PurchaseOrderPayload subscriptionPurchasesVerify(String jwsSubscriptionStatus) {
DecodedJWT decodedJWT = JWT.decode(jwsSubscriptionStatus);
//String header = decodedJWT.getHeader();
String payload = decodedJWT.getPayload();
// 前面应该不是base64编码后的官网说都是编码后的但是解码会报错
//String signature = decodedJWT.getSignature();
String decodeAppPayload = new String(Base64Utils.decode(payload.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
// String decodeHeader = new String(Base64Utils.decode(header.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
// String decodeSignature = new String(Base64Utils.decode(signature.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
SubGroupStatusPayload appSubGroupStatusPayload = JSON.parseObject(decodeAppPayload, SubGroupStatusPayload.class);
SubscriptionStatus lastSubscriptionStatus = appSubGroupStatusPayload.getLastSubscriptionStatus();
PurchaseOrderPayload lastPurchaseOrder = lastSubscriptionStatus.getLastPurchaseOrder();
// pack the request body
Map<String, String> bodyMap = new HashMap<>();
bodyMap.put("purchaseToken", verifyDTO.getPurchaseToken());
bodyMap.put("productId", verifyDTO.getProductId());
bodyMap.put("purchaseToken", lastPurchaseOrder.getPurchaseToken());
bodyMap.put("purchaseOrderId", lastPurchaseOrder.getPurchaseOrderId());
// construct the Authorization in Header
Map<String, String> headers = buildAuthorization(huaweiPaymentConfig.getAppId(), bodyMap);
String response = HttpUtil.createPost(VERIFY_TOKEN_URL)
// 订阅状态查询
String response = HttpUtil.createPost(ROOT_URL + SUBSCRIPTION_STATUS_QUERY_URL)
.addHeaders(headers)
.body(JSON.toJSONString(bodyMap))
.execute().body();
HuaweiPurchasesVerifyResponseDTO huaweiPurchasesVerifyResponseDTO = JSON.parseObject(response, HuaweiPurchasesVerifyResponseDTO.class);
InAppPurchaseData inAppPurchaseData = JSON.parseObject(huaweiPurchasesVerifyResponseDTO.getPurchaseTokenData(), InAppPurchaseData.class);
// 获取服务订单
OrderInfo orderInfo = orderInfoService.selectOrderInfoByOrderNo(verifyDTO.getOrderNo());
// 校验订单
boolean checkSuccessOrder = checkSuccessOrder(huaweiPurchasesVerifyResponseDTO.getPurchaseTokenData()
, huaweiPurchasesVerifyResponseDTO.getDataSignature()
, PUBLIC_KEY
, huaweiPurchasesVerifyResponseDTO.getSignatureAlgorithm()
, orderInfo);
log.info("订单状态查询返回信息:{}", response);
HuaweiQueryResponse huaweiQueryResponse = JSON.parseObject(response, HuaweiQueryResponse.class);
if (!"0".equals(huaweiQueryResponse.getResponseCode())) {
throw new RuntimeException("订单状态查询失败");
}
DecodedJWT huaweiQueryResponseDecodedJWT = JWT.decode(huaweiQueryResponse.getJwsSubGroupStatus());
String huaweiQueryResponsepayload = huaweiQueryResponseDecodedJWT.getPayload();
String decodeHuaweiQueryResponsepayload = new String(Base64Utils.decode(huaweiQueryResponsepayload.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
SubGroupStatusPayload subGroupStatusPayload = JSON.parseObject(decodeHuaweiQueryResponsepayload, SubGroupStatusPayload.class);
PurchaseOrderPayload huaweiQueryResponsePurchaseOrderPayload = subGroupStatusPayload.getLastSubscriptionStatus().getLastPurchaseOrder();
// 发货
return delivery(lastPurchaseOrder, huaweiQueryResponsePurchaseOrderPayload);
}
Assert.isTrue(checkSuccessOrder, "订单校验失败,请重试");
Assert.isTrue(inAppPurchaseData.getPurchaseState()==0, "订单未完成购买");
DateTime payTime = DateUtil.date(inAppPurchaseData.getPurchaseTime());
UserMember userMember = userMemberService.selectUserMemberByUserId(orderInfo.getCreateUserId());
/**
* 发货
* @param purchaseOrder
* @param huaweiQueryResponsePurchaseOrderPayload
* @return
*/
private PurchaseOrderPayload delivery(PurchaseOrderPayload purchaseOrder, PurchaseOrderPayload huaweiQueryResponsePurchaseOrderPayload) {
Assert.isTrue(purchaseOrder.getPurchaseOrderId().equals(huaweiQueryResponsePurchaseOrderPayload.getPurchaseOrderId()), "订单不一致,发货失败!");
if (2 == huaweiQueryResponsePurchaseOrderPayload.getFinishStatus()) {
// 还未发货
// 查询平台是否配置该商品
// 查询平台是否配置该商品
GoodsMessages goods = goodsMessagesService.selectGoodsMessagesByCode(huaweiQueryResponsePurchaseOrderPayload.getProductId());
Assert.notNull(goods, "未配置此商品,请检查商品配置");
Assert.isTrue(goods.getOriginalPrice().equals(huaweiQueryResponsePurchaseOrderPayload.getPrice()), "商品价格与订单价格不一致,请检查价格配置");
// 创建完成订单
createOrder(huaweiQueryResponsePurchaseOrderPayload, goods);
// 发放会员权益
distributeMembershipBenefits(huaweiQueryResponsePurchaseOrderPayload);
} else {
log.info("华为应用内支付订单已发货!{}", JSON.toJSONString(huaweiQueryResponsePurchaseOrderPayload));
}
return huaweiQueryResponsePurchaseOrderPayload;
}
private PurchaseOrderPayload consumablePurchasesVerify(String jwsPurchaseOrder) {
DecodedJWT decodedJWT = JWT.decode(jwsPurchaseOrder);
//String header = decodedJWT.getHeader();
String payload = decodedJWT.getPayload();
// 前面应该不是base64编码后的官网说都是编码后的但是解码会报错
//String signature = decodedJWT.getSignature();
String decodeAppPayload = new String(Base64Utils.decode(payload.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
// String decodeHeader = new String(Base64Utils.decode(header.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
// String decodeSignature = new String(Base64Utils.decode(signature.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
PurchaseOrderPayload appPurchaseOrderPayload = JSON.parseObject(decodeAppPayload, PurchaseOrderPayload.class);
// pack the request body
Map<String, String> bodyMap = new HashMap<>();
bodyMap.put("purchaseToken", appPurchaseOrderPayload.getPurchaseToken());
bodyMap.put("purchaseOrderId", appPurchaseOrderPayload.getPurchaseOrderId());
// construct the Authorization in Header
Map<String, String> headers = buildAuthorization(huaweiPaymentConfig.getAppId(), bodyMap);
// 订单状态查询
String response = HttpUtil.createPost(ROOT_URL + ORDER_STATUS_QUERY_URL)
.addHeaders(headers)
.body(JSON.toJSONString(bodyMap))
.execute().body();
log.info("订单状态查询返回信息:{}", response);
HuaweiQueryResponse huaweiQueryResponse = JSON.parseObject(response, HuaweiQueryResponse.class);
if (!"0".equals(huaweiQueryResponse.getResponseCode())) {
throw new RuntimeException("订单状态查询失败");
}
DecodedJWT huaweiQueryResponseDecodedJWT = JWT.decode(huaweiQueryResponse.getJwsPurchaseOrder());
// String huaweiQueryResponseHeader = huaweiQueryResponseDecodedJWT.getHeader();
String huaweiQueryResponsepayload = huaweiQueryResponseDecodedJWT.getPayload();
// String huaweiQueryResponsesignature = huaweiQueryResponseDecodedJWT.getSignature();
//String decodehuaweiQueryResponseHeader = new String(Base64Utils.decode(huaweiQueryResponseHeader.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
String decodehuaweiQueryResponsepayload = new String(Base64Utils.decode(huaweiQueryResponsepayload.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
// String decodehuaweiQueryResponsesignature = new String(Base64Utils.decode(huaweiQueryResponsesignature.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
PurchaseOrderPayload huaweiQueryResponsePurchaseOrderPayload = JSON.parseObject(decodehuaweiQueryResponsepayload, PurchaseOrderPayload.class);
// 暂时只做简单验证
return delivery(appPurchaseOrderPayload, huaweiQueryResponsePurchaseOrderPayload);
}
private void createOrder(PurchaseOrderPayload appPurchaseOrderPayload, GoodsMessages goods) {
Long userId = SecurityUtils.getUserId();
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderNo(snowflakeIdWorker.nextId());
orderInfo.setOrderStatus(6L);
orderInfo.setPayType(1L);
orderInfo.setPayChannel(2L);
orderInfo.setOrderAmt(appPurchaseOrderPayload.getPrice());
orderInfo.setPayAmt(appPurchaseOrderPayload.getPrice());
orderInfo.setReallyAmt(appPurchaseOrderPayload.getPrice());
orderInfo.setReceiveType(0L);
orderInfo.setGoodsId(goods.getId());
orderInfo.setPayTime(DateUtil.date(appPurchaseOrderPayload.getPurchaseTime()));
orderInfo.setCreateUserId(userId);
orderInfo.setUpdateUserId(userId);
orderInfo.setOutOrderNo(appPurchaseOrderPayload.getPurchaseOrderId());
orderInfo.setPayData(JSON.toJSONString(appPurchaseOrderPayload));
orderInfo.setGoodsPrice(goods.getOriginalPrice());
orderInfo.setGoodsType(goods.getGoodsType());
orderInfo.setGoodsCode(goods.getGoodsCode());
orderInfoService.insertOrder(orderInfo);
}
private void distributeMembershipBenefits(PurchaseOrderPayload purchaseOrderPayload) {
// 发放会员权益
Long userId = SecurityUtils.getUserId();
UserMember userMember = userMemberService.selectUserMemberByUserId(userId);
if (userMember == null) {
// 添加会员信息
boolean isSubscription = verifyDTO.getKind() == 2;
boolean isSubscription = GoodsConstants.GOODS_TYPE_AUTOMATIC_RENEWAL_SUBSCRIPTION.equals(purchaseOrderPayload.getProductType());
DateTime payTime = DateUtil.date(purchaseOrderPayload.getPurchaseTime());
userMember = new UserMember();
userMember.setMemberLevel(isSubscription ? 1 : 2);
userMember.setSubscriptionStatus(isSubscription ? 1 : 0);
userMember.setUserId(orderInfo.getCreateUserId());
userMember.setUserId(userId);
userMember.setIntegration(0L);
DateTime expirationTime = DateUtil.offset(payTime, DateField.MONTH, 1);
userMember.setExpirationTime(expirationTime);
@ -110,48 +262,56 @@ public class HuaweiPaymentServiceImpl implements IHuaweiPaymentService {
userMember.setUpdateTime(new Date());
userMemberService.updateUserMember(userMember);
}
// 更新订单状态
orderInfo.setPayTime(payTime);
orderInfo.setOrderStatus(3L);
orderInfo.setPayChannel(2L);
orderInfo.setPayAmt(orderInfo.getOrderAmt());
orderInfo.setReallyAmt(orderInfo.getOrderAmt());
orderInfo.setOrderStatus(3L);
orderInfo.setPayData(huaweiPurchasesVerifyResponseDTO.getPurchaseTokenData());
orderInfoService.updateOrderInfo(orderInfo);
}
/**
* Gets App Level AccessToken.
*/
public static String getAppAT(String clientId, String clientSecret) {
// fetch accessToken
Map<String, Object> form = new HashMap<>(8);
form.put("grant_type", "client_credentials");
form.put("client_secret", clientSecret);
form.put("client_id", clientId);
String atResponse = HttpUtil.post(TOKEN_URL, form);
log.info("getAppAT Response : {}", atResponse);
JSONObject parseObject = JSON.parseObject(atResponse);
return parseObject.getString("access_token");
}
/**
* Build Authorization in Header
*
* @param appAt appAt
* @return headers
*/
public static Map<String, String> buildAuthorization(String appAt) {
String oriString = MessageFormat.format("APPAT:{0}", appAt);
String authorization =
MessageFormat.format("Basic {0}", Base64.encodeBase64String(oriString.getBytes(StandardCharsets.UTF_8)));
public static Map<String, String> buildAuthorization(String appId, Map<String, String> body) {
Map<String, Object> jwtHeader = new HashMap<>(8);
jwtHeader.put("alg", "ES256");
jwtHeader.put("typ", "JWT");
jwtHeader.put("kid", "0ae3e1be-374b-43a5-a297-045addbf76eb");
Map<String, Object> jwtPayload = new HashMap<>(8);
jwtPayload.put("iss", "f59509e6-dd17-4644-b832-ff05233146c8");
jwtPayload.put("aud", "iap-v1");
jwtPayload.put("iat", DateUtil.currentSeconds());
jwtPayload.put("exp", DateUtil.currentSeconds() + 1800L); // 半小时过期
jwtPayload.put("aid", appId);
jwtPayload.put("digest", getJwtPayloadDigest(body));
String token = HuaweiTokenGenerator.createToken(jwtHeader, jwtPayload);
// String authorization = MessageFormat.format("Basic {0}", Base64.encodeBase64String(oriString.getBytes(StandardCharsets.UTF_8)));
String authorization = MessageFormat.format("Bearer {0}", token);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", authorization);
headers.put("Content-Type", "application/json; charset=UTF-8");
return headers;
}
private static String getJwtPayloadDigest(Map<String, String> body) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(JSON.toJSONString(body).getBytes(StandardCharsets.UTF_8));
byte[] digestByte = messageDigest.digest();
StringBuilder stringBuffer = new StringBuilder();
String temp;
for (byte aByte : digestByte) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 校验签名信息校验InAppPurchaseData中的productIdpricecurrency等信息的一致性
*
@ -173,15 +333,15 @@ public class HuaweiPaymentServiceImpl implements IHuaweiPaymentService {
/**
* 校验InAppPurchaseData中的productIdpricecurrency等信息的一致性
*
* @param content 结果字符串
* @param yourOrderInfo 您的订单信息包括productIdpricecurrency
* @param content 结果字符串
* @param orderInfo 您的订单信息包括productIdpricecurrency
* @return 是否校验通过
*/
public static boolean checkProductIdAndPriceAndCurrency(String content, OrderInfo yourOrderInfo) {
public static boolean checkProductIdAndPriceAndCurrency(String content, OrderInfo orderInfo) {
InAppPurchaseData inAppPurchaseData = JSON.parseObject(content, InAppPurchaseData.class);
// 校验InAppPurchaseData中的productIdpricecurrency等信息的一致性
return inAppPurchaseData.getProductId().equals(yourOrderInfo.getGoodsId())
&& inAppPurchaseData.getPrice().equals(yourOrderInfo.getOrderAmt());
return inAppPurchaseData.getProductId().equals(orderInfo.getGoodsId())
&& inAppPurchaseData.getPrice().equals(orderInfo.getOrderAmt());
}
/**

View File

@ -0,0 +1,43 @@
package com.sf.payment.utils;
import cn.hutool.core.io.resource.ClassPathResource;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
public class HuaweiTokenGenerator {
public static String createToken(Map<String, Object> jwtHeader, Map<String, Object> jwtPayload) {
try {
// AppGallery Connect 华为应用内支付配置密钥下载私钥文件
ClassPathResource classPathResource = new ClassPathResource("IAPKey_0ae3e1be-374b-43a5-a297-045addbf76eb.p8");
InputStream IAPKeyStream = classPathResource.getStream();
String content = IOUtils.toString(IAPKeyStream, String.valueOf(StandardCharsets.UTF_8));
String privateKey = content
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll("\\R+", "")
.replace("-----END PRIVATE KEY-----", "");
KeyFactory keyFactory = KeyFactory.getInstance("EC");
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
ECPrivateKey ecPrivateKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
return JWT.create()
.withHeader(jwtHeader)
.withPayload(jwtPayload)
.sign(Algorithm.ECDSA256(ecPrivateKey));
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQggG04243qynU/yWYy
XpYVy9ZWMuLKzZiwhXCBWQBCOLigCgYIKoZIzj0DAQehRANCAARNln2/d+TM2pIO
LWQzvI77gPAVEvVCSlIuiJ+J7CJSG5KCysBaEeiiD5cc4dZWnUBijF8FBh7nDLaH
VwFXfrS+
-----END PRIVATE KEY-----

33
sf-service/pom.xml Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>smarterFramework</artifactId>
<groupId>com.smarterFramework</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sf-service</artifactId>
<description>
业务模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-common</artifactId>
</dependency>
<dependency>
<groupId>com.smarterFramework</groupId>
<artifactId>sf-framework</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,7 +1,10 @@
package com.sf.goods.controller;
package com.sf.service.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.sf.service.domain.GoodsMessages;
import com.sf.service.service.IGoodsMessagesService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@ -16,19 +19,17 @@ import com.sf.common.annotation.Log;
import com.sf.common.core.controller.BaseController;
import com.sf.common.core.domain.AjaxResult;
import com.sf.common.enums.BusinessType;
import com.sf.goods.domain.GoodsMessages;
import com.sf.goods.service.IGoodsMessagesService;
import com.sf.common.utils.poi.ExcelUtil;
import com.sf.common.core.page.TableDataInfo;
/**
* 商品信息Controller
*
* @author ztzh
* @date 2024-04-11
*
* @author zoukun
* @date 2024-04-18
*/
@RestController
@RequestMapping("/goods/goods")
@RequestMapping("/service/goods")
public class GoodsMessagesController extends BaseController
{
@Autowired
@ -37,7 +38,7 @@ public class GoodsMessagesController extends BaseController
/**
* 查询商品信息列表
*/
@PreAuthorize("@ss.hasPermi('goods:goods:list')")
@PreAuthorize("@ss.hasPermi('service:goods:list')")
@GetMapping("/list")
public TableDataInfo list(GoodsMessages goodsMessages)
{
@ -49,7 +50,7 @@ public class GoodsMessagesController extends BaseController
/**
* 导出商品信息列表
*/
@PreAuthorize("@ss.hasPermi('goods:goods:export')")
@PreAuthorize("@ss.hasPermi('service:goods:export')")
@Log(title = "商品信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, GoodsMessages goodsMessages)
@ -62,7 +63,7 @@ public class GoodsMessagesController extends BaseController
/**
* 获取商品信息详细信息
*/
@PreAuthorize("@ss.hasPermi('goods:goods:query')")
@PreAuthorize("@ss.hasPermi('service:goods:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
@ -72,7 +73,7 @@ public class GoodsMessagesController extends BaseController
/**
* 新增商品信息
*/
@PreAuthorize("@ss.hasPermi('goods:goods:add')")
@PreAuthorize("@ss.hasPermi('service:goods:add')")
@Log(title = "商品信息", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody GoodsMessages goodsMessages)
@ -83,7 +84,7 @@ public class GoodsMessagesController extends BaseController
/**
* 修改商品信息
*/
@PreAuthorize("@ss.hasPermi('goods:goods:edit')")
@PreAuthorize("@ss.hasPermi('service:goods:edit')")
@Log(title = "商品信息", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody GoodsMessages goodsMessages)
@ -94,9 +95,9 @@ public class GoodsMessagesController extends BaseController
/**
* 删除商品信息
*/
@PreAuthorize("@ss.hasPermi('goods:goods:remove')")
@PreAuthorize("@ss.hasPermi('service:goods:remove')")
@Log(title = "商品信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(goodsMessagesService.deleteGoodsMessagesByIds(ids));

View File

@ -1,4 +1,4 @@
package com.sf.goods.domain;
package com.sf.service.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -8,8 +8,8 @@ import com.sf.common.core.domain.BaseEntity;
/**
* 商品信息对象 GOODS_MESSAGES
*
* @author ztzh
* @date 2024-04-11
* @author zoukun
* @date 2024-04-18
*/
public class GoodsMessages extends BaseEntity
{
@ -26,6 +26,7 @@ public class GoodsMessages extends BaseEntity
private Long stockId;
/** 审核状态1通过0未通过 */
@Excel(name = "审核状态1通过0未通过")
private Long reviewStatus;
/** 商品标题 */
@ -36,16 +37,23 @@ public class GoodsMessages extends BaseEntity
@Excel(name = "商品图片")
private String productPicture;
/** 原价 */
@Excel(name = "原价")
/** 商品原价 */
@Excel(name = "商品原价")
private Long originalPrice;
/** 商品描述 */
@Excel(name = "商品描述")
private String productDesc;
/** 商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品 */
@Excel(name = "商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品")
private Integer goodsType;
/** 商品规格 */
@Excel(name = "商品规格")
private String goodsSpec;
/** 排序 */
@Excel(name = "排序")
private Long orderNum;
/** 逻辑删除,0:未删除,1:删除 */
@ -57,13 +65,12 @@ public class GoodsMessages extends BaseEntity
/** 更新人 */
private String modified;
/** 商品类型 */
@Excel(name = "商品类型")
private String goodsType;
/** 商品名称 */
private String goodsName;
/** 商品规格 */
@Excel(name = "商品规格")
private String goodsSpec;
/** 商品型号 */
@Excel(name = "商品型号")
private String goodsModel;
public void setId(Long id)
{
@ -137,6 +144,24 @@ public class GoodsMessages extends BaseEntity
{
return productDesc;
}
public void setGoodsType(Integer goodsType)
{
this.goodsType = goodsType;
}
public Integer getGoodsType()
{
return goodsType;
}
public void setGoodsSpec(String goodsSpec)
{
this.goodsSpec = goodsSpec;
}
public String getGoodsSpec()
{
return goodsSpec;
}
public void setOrderNum(Long orderNum)
{
this.orderNum = orderNum;
@ -173,23 +198,23 @@ public class GoodsMessages extends BaseEntity
{
return modified;
}
public void setGoodsType(String goodsType)
public void setGoodsName(String goodsName)
{
this.goodsType = goodsType;
this.goodsName = goodsName;
}
public String getGoodsType()
public String getGoodsName()
{
return goodsType;
return goodsName;
}
public void setGoodsSpec(String goodsSpec)
public void setGoodsModel(String goodsModel)
{
this.goodsSpec = goodsSpec;
this.goodsModel = goodsModel;
}
public String getGoodsSpec()
public String getGoodsModel()
{
return goodsSpec;
return goodsModel;
}
@Override
@ -203,14 +228,16 @@ public class GoodsMessages extends BaseEntity
.append("productPicture", getProductPicture())
.append("originalPrice", getOriginalPrice())
.append("productDesc", getProductDesc())
.append("goodsType", getGoodsType())
.append("goodsSpec", getGoodsSpec())
.append("orderNum", getOrderNum())
.append("isDelete", getIsDelete())
.append("created", getCreated())
.append("modified", getModified())
.append("createTime", getCreateTime())
.append("updateTime", getUpdateTime())
.append("goodsType", getGoodsType())
.append("goodsSpec", getGoodsSpec())
.append("goodsName", getGoodsName())
.append("goodsModel", getGoodsModel())
.toString();
}
}

View File

@ -1,13 +1,13 @@
package com.sf.goods.mapper;
package com.sf.service.mapper;
import java.util.List;
import com.sf.goods.domain.GoodsMessages;
import com.sf.service.domain.GoodsMessages;
/**
* 商品信息Mapper接口
*
* @author ztzh
* @date 2024-04-11
* @author zoukun
* @date 2024-04-18
*/
public interface GoodsMessagesMapper
{
@ -58,4 +58,6 @@ public interface GoodsMessagesMapper
* @return 结果
*/
public int deleteGoodsMessagesByIds(Long[] ids);
GoodsMessages selectGoodsMessagesByCode(String goodsCode);
}

View File

@ -1,13 +1,13 @@
package com.sf.goods.service;
package com.sf.service.service;
import java.util.List;
import com.sf.goods.domain.GoodsMessages;
import com.sf.service.domain.GoodsMessages;
/**
* 商品信息Service接口
*
* @author ztzh
* @date 2024-04-11
* @author zoukun
* @date 2024-04-18
*/
public interface IGoodsMessagesService
{
@ -58,4 +58,6 @@ public interface IGoodsMessagesService
* @return 结果
*/
public int deleteGoodsMessagesById(Long id);
GoodsMessages selectGoodsMessagesByCode(String goodsCode);
}

View File

@ -1,18 +1,19 @@
package com.sf.goods.service.impl;
package com.sf.service.service.impl;
import java.util.List;
import com.sf.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sf.goods.mapper.GoodsMessagesMapper;
import com.sf.goods.domain.GoodsMessages;
import com.sf.goods.service.IGoodsMessagesService;
import com.sf.service.mapper.GoodsMessagesMapper;
import com.sf.service.domain.GoodsMessages;
import com.sf.service.service.IGoodsMessagesService;
import org.springframework.util.StringUtils;
/**
* 商品信息Service业务层处理
*
* @author ztzh
* @date 2024-04-11
* @author zoukun
* @date 2024-04-18
*/
@Service
public class GoodsMessagesServiceImpl implements IGoodsMessagesService
@ -93,4 +94,12 @@ public class GoodsMessagesServiceImpl implements IGoodsMessagesService
{
return goodsMessagesMapper.deleteGoodsMessagesById(id);
}
@Override
public GoodsMessages selectGoodsMessagesByCode(String goodsCode) {
if (StringUtils.hasText(goodsCode)){
return goodsMessagesMapper.selectGoodsMessagesByCode(goodsCode);
}
return null;
}
}

View File

@ -1,118 +1,130 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sf.goods.mapper.GoodsMessagesMapper">
<resultMap type="GoodsMessages" id="GoodsMessagesResult">
<result property="id" column="id" />
<result property="goodsCode" column="goods_code" />
<result property="stockId" column="stock_id" />
<result property="reviewStatus" column="review_status" />
<result property="productTitle" column="product_title" />
<result property="productPicture" column="product_picture" />
<result property="originalPrice" column="original_price" />
<result property="productDesc" column="product_desc" />
<result property="orderNum" column="order_num" />
<result property="isDelete" column="is_delete" />
<result property="created" column="created" />
<result property="modified" column="modified" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="goodsType" column="goods_type" />
<result property="goodsSpec" column="goods_spec" />
</resultMap>
<sql id="selectGoodsMessagesVo">
select id, goods_code, stock_id, review_status, product_title, product_picture, original_price, product_desc, order_num, is_delete, created, modified, create_time, update_time, goods_type, goods_spec from GOODS_MESSAGES
</sql>
<select id="selectGoodsMessagesList" parameterType="GoodsMessages" resultMap="GoodsMessagesResult">
<include refid="selectGoodsMessagesVo"/>
<where>
<if test="goodsCode != null and goodsCode != ''"> and goods_code = #{goodsCode}</if>
<if test="productTitle != null and productTitle != ''"> and product_title like concat('%', #{productTitle}, '%')</if>
<if test="goodsType != null and goodsType != ''"> and goods_type = #{goodsType}</if>
</where>
</select>
<select id="selectGoodsMessagesById" parameterType="Long" resultMap="GoodsMessagesResult">
<include refid="selectGoodsMessagesVo"/>
where id = #{id}
</select>
<select id="selectGoodsMessagesByCode" parameterType="java.lang.String" resultMap="GoodsMessagesResult">
<include refid="selectGoodsMessagesVo"/>
where goods_code = #{code}
</select>
<insert id="insertGoodsMessages" parameterType="GoodsMessages" useGeneratedKeys="true" keyProperty="id">
insert into GOODS_MESSAGES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="goodsCode != null and goodsCode != ''">goods_code,</if>
<if test="stockId != null">stock_id,</if>
<if test="reviewStatus != null">review_status,</if>
<if test="productTitle != null and productTitle != ''">product_title,</if>
<if test="productPicture != null and productPicture != ''">product_picture,</if>
<if test="originalPrice != null">original_price,</if>
<if test="productDesc != null">product_desc,</if>
<if test="orderNum != null">order_num,</if>
<if test="isDelete != null">is_delete,</if>
<if test="created != null">created,</if>
<if test="modified != null">modified,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="goodsType != null and goodsType != ''">goods_type,</if>
<if test="goodsSpec != null and goodsSpec != ''">goods_spec,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="goodsCode != null and goodsCode != ''">#{goodsCode},</if>
<if test="stockId != null">#{stockId},</if>
<if test="reviewStatus != null">#{reviewStatus},</if>
<if test="productTitle != null and productTitle != ''">#{productTitle},</if>
<if test="productPicture != null and productPicture != ''">#{productPicture},</if>
<if test="originalPrice != null">#{originalPrice},</if>
<if test="productDesc != null">#{productDesc},</if>
<if test="orderNum != null">#{orderNum},</if>
<if test="isDelete != null">#{isDelete},</if>
<if test="created != null">#{created},</if>
<if test="modified != null">#{modified},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="goodsType != null and goodsType != ''">#{goodsType},</if>
<if test="goodsSpec != null and goodsSpec != ''">#{goodsSpec},</if>
</trim>
</insert>
<update id="updateGoodsMessages" parameterType="GoodsMessages">
update GOODS_MESSAGES
<trim prefix="SET" suffixOverrides=",">
<if test="goodsCode != null and goodsCode != ''">goods_code = #{goodsCode},</if>
<if test="stockId != null">stock_id = #{stockId},</if>
<if test="reviewStatus != null">review_status = #{reviewStatus},</if>
<if test="productTitle != null and productTitle != ''">product_title = #{productTitle},</if>
<if test="productPicture != null and productPicture != ''">product_picture = #{productPicture},</if>
<if test="originalPrice != null">original_price = #{originalPrice},</if>
<if test="productDesc != null">product_desc = #{productDesc},</if>
<if test="orderNum != null">order_num = #{orderNum},</if>
<if test="isDelete != null">is_delete = #{isDelete},</if>
<if test="created != null">created = #{created},</if>
<if test="modified != null">modified = #{modified},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="goodsType != null and goodsType != ''">goods_type = #{goodsType},</if>
<if test="goodsSpec != null and goodsSpec != ''">goods_spec = #{goodsSpec},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteGoodsMessagesById" parameterType="Long">
delete from GOODS_MESSAGES where id = #{id}
</delete>
<delete id="deleteGoodsMessagesByIds" parameterType="String">
delete from GOODS_MESSAGES where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sf.service.mapper.GoodsMessagesMapper">
<resultMap type="GoodsMessages" id="GoodsMessagesResult">
<result property="id" column="id" />
<result property="goodsCode" column="goods_code" />
<result property="stockId" column="stock_id" />
<result property="reviewStatus" column="review_status" />
<result property="productTitle" column="product_title" />
<result property="productPicture" column="product_picture" />
<result property="originalPrice" column="original_price" />
<result property="productDesc" column="product_desc" />
<result property="goodsType" column="goods_type" />
<result property="goodsSpec" column="goods_spec" />
<result property="orderNum" column="order_num" />
<result property="isDelete" column="is_delete" />
<result property="created" column="created" />
<result property="modified" column="modified" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="goodsName" column="goods_name" />
<result property="goodsModel" column="goods_model" />
</resultMap>
<sql id="selectGoodsMessagesVo">
select id, goods_code, stock_id, review_status, product_title, product_picture, original_price, product_desc, goods_type, goods_spec, order_num, is_delete, created, modified, create_time, update_time, goods_name, goods_model from GOODS_MESSAGES
</sql>
<select id="selectGoodsMessagesList" parameterType="GoodsMessages" resultMap="GoodsMessagesResult">
<include refid="selectGoodsMessagesVo"/>
<where>
<if test="goodsCode != null and goodsCode != ''"> and goods_code = #{goodsCode}</if>
<if test="reviewStatus != null "> and review_status = #{reviewStatus}</if>
<if test="productTitle != null and productTitle != ''"> and product_title like concat('%', #{productTitle}, '%')</if>
<if test="params.beginOriginalPrice != null and params.beginOriginalPrice != '' and params.endOriginalPrice != null and params.endOriginalPrice != ''"> and original_price between #{params.beginOriginalPrice} and #{params.endOriginalPrice}</if>
<if test="goodsType != null "> and goods_type = #{goodsType}</if>
<if test="goodsSpec != null and goodsSpec != ''"> and goods_spec = #{goodsSpec}</if>
<if test="goodsModel != null and goodsModel != ''"> and goods_model = #{goodsModel}</if>
</where>
</select>
<select id="selectGoodsMessagesById" parameterType="Long" resultMap="GoodsMessagesResult">
<include refid="selectGoodsMessagesVo"/>
where id = #{id}
</select>
<select id="selectGoodsMessagesByCode" parameterType="String" resultMap="GoodsMessagesResult">
<include refid="selectGoodsMessagesVo"/>
where goods_code = #{goodsCode}
</select>
<insert id="insertGoodsMessages" parameterType="GoodsMessages" useGeneratedKeys="true" keyProperty="id">
insert into GOODS_MESSAGES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="goodsCode != null and goodsCode != ''">goods_code,</if>
<if test="stockId != null">stock_id,</if>
<if test="reviewStatus != null">review_status,</if>
<if test="productTitle != null and productTitle != ''">product_title,</if>
<if test="productPicture != null and productPicture != ''">product_picture,</if>
<if test="originalPrice != null">original_price,</if>
<if test="productDesc != null">product_desc,</if>
<if test="goodsType != null">goods_type,</if>
<if test="goodsSpec != null and goodsSpec != ''">goods_spec,</if>
<if test="orderNum != null">order_num,</if>
<if test="isDelete != null">is_delete,</if>
<if test="created != null">created,</if>
<if test="modified != null">modified,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="goodsName != null and goodsName != ''">goods_name,</if>
<if test="goodsModel != null">goods_model,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="goodsCode != null and goodsCode != ''">#{goodsCode},</if>
<if test="stockId != null">#{stockId},</if>
<if test="reviewStatus != null">#{reviewStatus},</if>
<if test="productTitle != null and productTitle != ''">#{productTitle},</if>
<if test="productPicture != null and productPicture != ''">#{productPicture},</if>
<if test="originalPrice != null">#{originalPrice},</if>
<if test="productDesc != null">#{productDesc},</if>
<if test="goodsType != null">#{goodsType},</if>
<if test="goodsSpec != null and goodsSpec != ''">#{goodsSpec},</if>
<if test="orderNum != null">#{orderNum},</if>
<if test="isDelete != null">#{isDelete},</if>
<if test="created != null">#{created},</if>
<if test="modified != null">#{modified},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="goodsName != null and goodsName != ''">#{goodsName},</if>
<if test="goodsModel != null">#{goodsModel},</if>
</trim>
</insert>
<update id="updateGoodsMessages" parameterType="GoodsMessages">
update GOODS_MESSAGES
<trim prefix="SET" suffixOverrides=",">
<if test="goodsCode != null and goodsCode != ''">goods_code = #{goodsCode},</if>
<if test="stockId != null">stock_id = #{stockId},</if>
<if test="reviewStatus != null">review_status = #{reviewStatus},</if>
<if test="productTitle != null and productTitle != ''">product_title = #{productTitle},</if>
<if test="productPicture != null and productPicture != ''">product_picture = #{productPicture},</if>
<if test="originalPrice != null">original_price = #{originalPrice},</if>
<if test="productDesc != null">product_desc = #{productDesc},</if>
<if test="goodsType != null">goods_type = #{goodsType},</if>
<if test="goodsSpec != null and goodsSpec != ''">goods_spec = #{goodsSpec},</if>
<if test="orderNum != null">order_num = #{orderNum},</if>
<if test="isDelete != null">is_delete = #{isDelete},</if>
<if test="created != null">created = #{created},</if>
<if test="modified != null">modified = #{modified},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="goodsName != null and goodsName != ''">goods_name = #{goodsName},</if>
<if test="goodsModel != null">goods_model = #{goodsModel},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteGoodsMessagesById" parameterType="Long">
delete from GOODS_MESSAGES where id = #{id}
</delete>
<delete id="deleteGoodsMessagesByIds" parameterType="String">
delete from GOODS_MESSAGES where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -1,44 +1,44 @@
import request from '@/utils/request'
// 查询商品信息列表
export function listGoods(query) {
return request({
url: '/goods/goods/list',
method: 'get',
params: query
})
}
// 查询商品信息详细
export function getGoods(id) {
return request({
url: '/goods/goods/' + id,
method: 'get'
})
}
// 新增商品信息
export function addGoods(data) {
return request({
url: '/goods/goods',
method: 'post',
data: data
})
}
// 修改商品信息
export function updateGoods(data) {
return request({
url: '/goods/goods',
method: 'put',
data: data
})
}
// 删除商品信息
export function delGoods(id) {
return request({
url: '/goods/goods/' + id,
method: 'delete'
})
}
import request from '@/utils/request'
// 查询商品信息列表
export function listGoods(query) {
return request({
url: '/service/goods/list',
method: 'get',
params: query
})
}
// 查询商品信息详细
export function getGoods(id) {
return request({
url: '/service/goods/' + id,
method: 'get'
})
}
// 新增商品信息
export function addGoods(data) {
return request({
url: '/service/goods',
method: 'post',
data: data
})
}
// 修改商品信息
export function updateGoods(data) {
return request({
url: '/service/goods',
method: 'put',
data: data
})
}
// 删除商品信息
export function delGoods(id) {
return request({
url: '/service/goods/' + id,
method: 'delete'
})
}

View File

@ -1,348 +1,423 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="商品编号" prop="goodsCode">
<el-input
v-model="queryParams.goodsCode"
placeholder="请输入商品编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="商品标题" prop="productTitle">
<el-input
v-model="queryParams.productTitle"
placeholder="请输入商品标题"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="商品类型" prop="goodsType">
<el-select v-model="queryParams.goodsType" placeholder="请选择商品类型" clearable>
<el-option
v-for="dict in dict.type.goods_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['goods:goods:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['goods:goods:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['goods:goods:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['goods:goods:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="goodsList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="商品id" align="center" prop="id" />
<el-table-column label="商品编号" align="center" prop="goodsCode" />
<el-table-column label="商品标题" align="center" prop="productTitle" />
<el-table-column label="商品图片" align="center" prop="productPicture" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.productPicture" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="价格" align="center" prop="originalPrice" />
<el-table-column label="商品描述" align="center" prop="productDesc" />
<el-table-column label="排序" align="center" prop="orderNum" />
<el-table-column label="商品类型" align="center" prop="goodsType">
<template slot-scope="scope">
<dict-tag :options="dict.type.goods_type" :value="scope.row.goodsType"/>
</template>
</el-table-column>
<el-table-column label="商品规格" align="center" prop="goodsSpec" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['goods:goods:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['goods:goods:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改商品信息对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="商品编号" prop="goodsCode">
<el-input v-model="form.goodsCode" placeholder="请输入商品编号" />
</el-form-item>
<el-form-item label="商品标题" prop="productTitle">
<el-input v-model="form.productTitle" placeholder="请输入商品标题" />
</el-form-item>
<el-form-item label="商品图片" prop="productPicture">
<image-upload v-model="form.productPicture"/>
</el-form-item>
<el-form-item label="原价格" prop="originalPrice">
<el-input v-model="form.originalPrice" placeholder="请输入原价格" />
</el-form-item>
<el-form-item label="商品描述" prop="productDesc">
<el-input v-model="form.productDesc" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="排序" prop="orderNum">
<el-input v-model="form.orderNum" placeholder="请输入排序" />
</el-form-item>
<el-form-item label="商品类型" prop="goodsType">
<el-radio-group v-model="form.goodsType">
<el-radio
v-for="dict in dict.type.goods_type"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品规格" prop="goodsSpec">
<el-input v-model="form.goodsSpec" placeholder="请输入商品规格" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listGoods, getGoods, delGoods, addGoods, updateGoods } from "@/api/goods/goods";
export default {
name: "Goods",
dicts: ['sys_notice_status', 'goods_type'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
goodsList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
goodsCode: null,
productTitle: null,
goodsType: null,
},
//
form: {},
//
rules: {
goodsCode: [
{ required: true, message: "商品编号不能为空", trigger: "blur" }
],
productTitle: [
{ required: true, message: "商品标题不能为空", trigger: "blur" }
],
productPicture: [
{ required: true, message: "商品图片不能为空", trigger: "blur" }
],
originalPrice: [
{ required: true, message: "原价格不能为空", trigger: "blur" }
],
orderNum: [
{ required: true, message: "排序不能为空", trigger: "blur" }
],
createTime: [
{ required: true, message: "创建时间不能为空", trigger: "blur" }
],
goodsType: [
{ required: true, message: "商品类型不能为空", trigger: "change" }
],
goodsSpec: [
{ required: true, message: "商品规格不能为空", trigger: "blur" }
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查询商品信息列表 */
getList() {
this.loading = true;
listGoods(this.queryParams).then(response => {
this.goodsList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
goodsCode: null,
stockId: null,
reviewStatus: null,
productTitle: null,
productPicture: null,
originalPrice: null,
productDesc: null,
orderNum: null,
isDelete: null,
created: null,
modified: null,
createTime: null,
updateTime: null,
goodsType: null,
goodsSpec: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加商品信息";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getGoods(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改商品信息";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
let data = Object.assign({},this.form)
delete data.explain
updateGoods(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addGoods(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除商品信息编号为"' + ids + '"的数据项?').then(function() {
return delGoods(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('goods/goods/export', {
...this.queryParams
}, `goods_${new Date().getTime()}.xlsx`)
}
}
};
</script>
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="商品编号" prop="goodsCode">
<el-input
v-model="queryParams.goodsCode"
placeholder="请输入商品编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="审核状态1通过0未通过" prop="reviewStatus">
<el-select v-model="queryParams.reviewStatus" placeholder="请选择审核状态1通过0未通过" clearable>
<el-option
v-for="dict in dict.type.goods_review_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="商品标题" prop="productTitle">
<el-input
v-model="queryParams.productTitle"
placeholder="请输入商品标题"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="商品原价" prop="originalPrice">
<el-input
v-model="queryParams.originalPrice"
placeholder="请输入商品原价"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品" prop="goodsType">
<el-select v-model="queryParams.goodsType" placeholder="请选择商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品" clearable>
<el-option
v-for="dict in dict.type.goods_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="商品规格" prop="goodsSpec">
<el-input
v-model="queryParams.goodsSpec"
placeholder="请输入商品规格"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="商品型号" prop="goodsModel">
<el-input
v-model="queryParams.goodsModel"
placeholder="请输入商品型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['service:goods:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['service:goods:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['service:goods:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['service:goods:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="goodsList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键" align="center" prop="id" />
<el-table-column label="商品编号" align="center" prop="goodsCode" />
<el-table-column label="审核状态1通过0未通过" align="center" prop="reviewStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.goods_review_status" :value="scope.row.reviewStatus"/>
</template>
</el-table-column>
<el-table-column label="商品标题" align="center" prop="productTitle" />
<el-table-column label="商品图片" align="center" prop="productPicture" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.productPicture" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="商品原价" align="center" prop="originalPrice" />
<el-table-column label="商品描述" align="center" prop="productDesc" />
<el-table-column label="商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品" align="center" prop="goodsType">
<template slot-scope="scope">
<dict-tag :options="dict.type.goods_type" :value="scope.row.goodsType"/>
</template>
</el-table-column>
<el-table-column label="商品规格" align="center" prop="goodsSpec" />
<el-table-column label="商品型号" align="center" prop="goodsModel" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['service:goods:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['service:goods:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改商品信息对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="商品编号" prop="goodsCode">
<el-input v-model="form.goodsCode" placeholder="请输入商品编号" />
</el-form-item>
<el-form-item label="库存表编号" prop="stockId">
<el-input v-model="form.stockId" placeholder="请输入库存表编号" />
</el-form-item>
<el-form-item label="审核状态1通过0未通过" prop="reviewStatus">
<el-radio-group v-model="form.reviewStatus">
<el-radio
v-for="dict in dict.type.goods_review_status"
:key="dict.value"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品标题" prop="productTitle">
<el-input v-model="form.productTitle" placeholder="请输入商品标题" />
</el-form-item>
<el-form-item label="商品图片" prop="productPicture">
<image-upload v-model="form.productPicture"/>
</el-form-item>
<el-form-item label="商品原价" prop="originalPrice">
<el-input v-model="form.originalPrice" placeholder="请输入商品原价" />
</el-form-item>
<el-form-item label="商品描述" prop="productDesc">
<el-input v-model="form.productDesc" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品" prop="goodsType">
<el-radio-group v-model="form.goodsType">
<el-radio
v-for="dict in dict.type.goods_type"
:key="dict.value"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品规格" prop="goodsSpec">
<el-input v-model="form.goodsSpec" placeholder="请输入商品规格" />
</el-form-item>
<el-form-item label="排序" prop="orderNum">
<el-input v-model="form.orderNum" placeholder="请输入排序" />
</el-form-item>
<el-form-item label="逻辑删除,0:未删除,1:删除" prop="isDelete">
<el-input v-model="form.isDelete" placeholder="请输入逻辑删除,0:未删除,1:删除" />
</el-form-item>
<el-form-item label="创建人" prop="created">
<el-input v-model="form.created" placeholder="请输入创建人" />
</el-form-item>
<el-form-item label="更新人" prop="modified">
<el-input v-model="form.modified" placeholder="请输入更新人" />
</el-form-item>
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="form.goodsName" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="商品型号" prop="goodsModel">
<el-input v-model="form.goodsModel" placeholder="请输入商品型号" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listGoods, getGoods, delGoods, addGoods, updateGoods } from "@/api/service/goods";
export default {
name: "Goods",
dicts: ['goods_review_status', 'goods_type'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
goodsList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
goodsCode: null,
reviewStatus: null,
productTitle: null,
originalPrice: null,
goodsType: null,
goodsSpec: null,
goodsModel: null
},
//
form: {},
//
rules: {
goodsCode: [
{ required: true, message: "商品编号不能为空", trigger: "blur" }
],
productTitle: [
{ required: true, message: "商品标题不能为空", trigger: "blur" }
],
productPicture: [
{ required: true, message: "商品图片不能为空", trigger: "blur" }
],
originalPrice: [
{ required: true, message: "商品原价不能为空", trigger: "blur" }
],
goodsType: [
{ required: true, message: "商品类型。 * • 0消耗型商品 * • 1非消耗型商品 * • 2自动续期订阅商品不能为空", trigger: "change" }
],
goodsSpec: [
{ required: true, message: "商品规格不能为空", trigger: "blur" }
],
orderNum: [
{ required: true, message: "排序不能为空", trigger: "blur" }
],
createTime: [
{ required: true, message: "创建时间不能为空", trigger: "blur" }
],
goodsName: [
{ required: true, message: "商品名称不能为空", trigger: "blur" }
],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询商品信息列表 */
getList() {
this.loading = true;
listGoods(this.queryParams).then(response => {
this.goodsList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
goodsCode: null,
stockId: null,
reviewStatus: null,
productTitle: null,
productPicture: null,
originalPrice: null,
productDesc: null,
goodsType: null,
goodsSpec: null,
orderNum: null,
isDelete: null,
created: null,
modified: null,
createTime: null,
updateTime: null,
goodsName: null,
goodsModel: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加商品信息";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getGoods(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改商品信息";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
let data = Object.assign({},this.form)
delete data.explain
updateGoods(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addGoods(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除商品信息编号为"' + ids + '"的数据项?').then(function() {
return delGoods(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('service/goods/export', {
...this.queryParams
}, `goods_${new Date().getTime()}.xlsx`)
}
}
};
</script>