admin
2025-07-30 cc5cf127da76d03ce7086da4d70f34b20e9803e0
功能完善
23个文件已添加
18个文件已修改
2837 ■■■■■ 已修改文件
src/main/java/com/taoke/autopay/controller/admin/AdminClientInfoController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/controller/admin/AdminClientInfoJS2Controller.java 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/controller/admin/AdminOrderController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dao/ClientAdditionalInfoMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dao/ClientInfoMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dao/OrderTaskExecutionDetailMapper.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dao/OrderTaskMapper.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dao/credit/CreditExchangeRecordMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dto/admin/OrderExcelDataDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dto/js2/ClientCountDTO.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/entity/ClientAdditionalInfo.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/entity/ClientInfo.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/entity/js2/OrderTask.java 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/entity/js2/OrderTaskExecutionDetail.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/exception/OrderTaskException.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/exception/OrderTaskExecutionDetailException.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/factory/OrderFactory.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/manager/UserCreditExchangeManager.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/ClientAdditionalInfoService.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/ClientInfoService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/ClientAdditionalInfoServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/ClientInfoServiceImpl.java 68 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskExecutionDetailServiceImpl.java 384 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskServiceImpl.java 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/js2/OrderTaskExecutionDetailService.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/js2/OrderTaskService.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/task/KeyOrderDistributeTask.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/utils/Constant.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/vo/admin/AdminOrderClientInfoVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-pro.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/ClientAdditionalInfoMapper.xml 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/ClientInfoMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/CreditExchangeRecordMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/OrderTaskExecutionDetailMapper.xml 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/OrderTaskMapper.xml 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/index.html 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-client-list.html 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-client-update.html 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/taoke/autopay/dao/ClientAdditionalInfoMapperTest.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/controller/admin/AdminClientInfoController.java
@@ -64,6 +64,7 @@
        }
        query.start = (page-1)*limit;
        query.count = limit;
        query.clientType = ClientInfo.CLIENT_TYPE_AGENT_PAYMENT;
        query.sortList= Arrays.asList(new String[]{"create_time desc"});
        JSONObject data=new JSONObject();
        List<ClientInfo> list = clientInfoService.list(query);
@@ -90,8 +91,10 @@
            return JsonUtil.loadFalseResult("单次创建最多100个");
        }
        for(int i=0;i<count;i++){
              ClientInfo info=new ClientInfo();
              info.setPwd(StringUtil.Md5(pwd));
              ClientInfo info=ClientInfo.builder()
                      .pwd(StringUtil.Md5(pwd))
                      .clientType(ClientInfo.CLIENT_TYPE_AGENT_PAYMENT)
                      .build();
              clientInfoService.add(info);
        }
        return JsonUtil.loadTrueResult("");
src/main/java/com/taoke/autopay/controller/admin/AdminClientInfoJS2Controller.java
New file
@@ -0,0 +1,154 @@
package com.taoke.autopay.controller.admin;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.taoke.autopay.dao.ClientInfoMapper;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import com.taoke.autopay.entity.ClientInfo;
import com.taoke.autopay.service.ClientAdditionalInfoService;
import com.taoke.autopay.service.ClientInfoService;
import com.taoke.autopay.utils.Constant;
import com.taoke.autopay.utils.TimeUtil;
import com.taoke.autopay.vo.admin.AdminOrderClientInfoVO;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.yeshi.utils.JsonUtil;
import org.yeshi.utils.StringUtil;
import org.yeshi.utils.encrypt.AESUtil;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
/**
 * 下单设备管理
 */
@Controller
@RequestMapping("/admin/api/clientinfo/js2")
public class AdminClientInfoJS2Controller {
    @Resource
    private ClientInfoService clientInfoService;
    @Resource
    private ClientAdditionalInfoService clientAdditionalInfoService;
    private final Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new TypeAdapter<Date>() {
        @Override
        public void write(JsonWriter out, Date value) throws IOException {
            String desc = "";
            if (value != null) {
                // 判断是否是同一天
                desc = TimeUtil.getGernalTime(value.getTime(), "yyyy-MM-dd HH:mm:ss");
                out.value(desc);
            } else {
                out.value("");
            }
        }
        @Override
        public Date read(JsonReader in) throws IOException {
            return new Date();
        }
    }).create();
    @ResponseBody
    @RequestMapping("list")
    public String list(String account, int page,int limit){
        ClientInfoMapper.DaoQuery query=new   ClientInfoMapper.DaoQuery();
        if(!StringUtil.isNullOrEmpty(account)) {
            query.account = account;
        }
        query.start = (long) (page - 1) *limit;
        query.count = limit;
        query.clientType = ClientInfo.CLIENT_TYPE_ORDER;
        query.sortList= Arrays.asList(new String[]{"create_time desc"});
        JSONObject data=new JSONObject();
        List<ClientInfo> list = clientInfoService.list(query);
        Map<Long, ClientAdditionalInfo> map;
        if(!list.isEmpty()){
            List<Long> idList=new ArrayList<>();
            for(ClientInfo info:list){
                idList.add(info.getId());
            }
            map = clientAdditionalInfoService.getClientAdditionalInfoMap(idList);
        } else {
            map = new HashMap<>();
        }
        List<AdminOrderClientInfoVO> results = new ArrayList<>();
        list.forEach(info->{
            ClientAdditionalInfo additionalInfo = map.get(info.getId());
            results.add(AdminOrderClientInfoVO.builder()
                    .client(info)
                    .additionalInfo(additionalInfo)
                    .build());
        });
        data.put("list", gson.toJson(results));
        data.put("count",  clientInfoService.count(query));
        return JsonUtil.loadTrueResult(data);
    }
    @ResponseBody
    @RequestMapping("setpwd")
    public String setPwd(Long id, String pwd) {
        if(id==null||pwd==null||pwd.length()<6){
            return JsonUtil.loadFalseResult("数据不完整");
        }
        clientInfoService.setPwd(id,StringUtil.Md5( pwd));
        return JsonUtil.loadTrueResult("");
    }
    @ResponseBody
    @RequestMapping("add")
    public String addClient(String pwd, int count) {
        if(count>100){
            return JsonUtil.loadFalseResult("单次创建最多100个");
        }
        for(int i=0;i<count;i++){
              ClientInfo info=ClientInfo.builder()
                      .pwd(StringUtil.Md5(pwd))
                      .clientType(ClientInfo.CLIENT_TYPE_ORDER)
                      .build();
              clientInfoService.add(info);
              if(info!=null){
                  clientAdditionalInfoService.addClientAdditionalInfo(ClientAdditionalInfo.builder()
                                  .id(info.getId())
                                  .createTime(new Date())
                          .build());
              }
        }
        return JsonUtil.loadTrueResult("");
    }
    @ResponseBody
    @RequestMapping("update")
    public String updateClientInfo(ClientAdditionalInfo info) {
        if(info==null||info.getId()==null) {
            return JsonUtil.loadFalseResult("数据不完整");
        }
        if(!StringUtil.isNullOrEmpty(info.getAlipayPassword())){
            info.setAlipayPassword(AESUtil.encrypt(info.getAlipayPassword(), Constant.AES_KEY));
        }
        clientAdditionalInfoService.updateClientAdditionalInfo(info);
        return JsonUtil.loadTrueResult("");
    }
    @ResponseBody
    @RequestMapping("getAdditionalInfo")
    public String getAdditionalInfo(Long id) {
        if(id==null) {
            return JsonUtil.loadFalseResult("数据不完整");
        }
        ClientAdditionalInfo additionalInfo =  clientAdditionalInfoService.getClientAdditionalInfoByClientId(id);
        return JsonUtil.loadTrueResult(gson.toJson(additionalInfo));
    }
}
src/main/java/com/taoke/autopay/controller/admin/AdminOrderController.java
@@ -312,6 +312,7 @@
                }else{
                    dto.setIgnore(order.getIgnore()?"是":"否");
                }
                dto.setOrderStateDesc(OrderFactory.getOrderStateDesc(order.getOrderState(), order.getOrderType()));
                dataList.add(dto);
            }
src/main/java/com/taoke/autopay/dao/ClientAdditionalInfoMapper.java
New file
@@ -0,0 +1,37 @@
package com.taoke.autopay.dao;
import java.lang.String;
import java.util.Date;
import java.lang.Long;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import org.yeshi.utils.mybatis.BaseMapper;
public interface ClientAdditionalInfoMapper extends BaseMapper<ClientAdditionalInfo> {
    ClientAdditionalInfo selectByPrimaryKeyForUpdate(@Param("id") Long id);
    List<ClientAdditionalInfo> list(@Param("query") DaoQuery query);
    List<ClientAdditionalInfo> listByIds(@Param("ids") List<Long> ids);
    long count(@Param("query") DaoQuery query);
    public static class DaoQuery{
        public Long id;
        public String mobile;
        public String alipayAccount;
        public String alipayPassword;
        public Date minCreateTime;
        public Date maxCreateTime;
        public Date minUpdateTime;
        public Date maxUpdateTime;
        // 移除了clientIds字段
        public long start;
        public int count;
        public List<String> sortList;
    }
}
src/main/java/com/taoke/autopay/dao/ClientInfoMapper.java
@@ -32,6 +32,7 @@
        public Date minActiveTime;
        public Date maxActiveTime;
        public Integer rule;
        public Integer clientType;
        public long start;
        public int count;
        public List<String> sortList;
src/main/java/com/taoke/autopay/dao/OrderTaskExecutionDetailMapper.java
New file
@@ -0,0 +1,54 @@
package com.taoke.autopay.dao;
import com.taoke.autopay.dto.js2.ClientCountDTO;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.yeshi.utils.mybatis.BaseMapper;
public interface OrderTaskExecutionDetailMapper extends BaseMapper<OrderTaskExecutionDetail> {
    OrderTaskExecutionDetail selectByPrimaryKey(@Param("id") String id);
    OrderTaskExecutionDetail selectByPrimaryKeyForUpdate(@Param("id") String id);
    int deleteByPrimaryKey(@Param("id") String id);
    List<OrderTaskExecutionDetail> list(@Param("query") DaoQuery query);
    List<OrderTaskExecutionDetail> listByIds(@Param("ids") List<String> ids);
    long count(@Param("query") DaoQuery query);
    /**
     * 根据执行状态分组统计clientId的数量
     * @return 包含执行状态和对应clientId数量的映射列表
     */
    List<ClientCountDTO> statisticClientIdsCountByStatus(@Param("statusList") List<Integer> statusList);
    public static class DaoQuery{
        public String id;
        public Long taskId;
        public Long clientId;
        public Integer executionStatus;
        public String statusDescription;
        public Date executionTime;
        public String orderNo;
        public String productName;
        public String shopName;
        public String couponCode;
        public Date orderTime;
        public Date receiveTime;
        public Date reviewTime;
        public Date minCreateTime;
        public Date maxCreateTime;
        public Date minUpdateTime;
        public Date maxUpdateTime;
        public long start;
        public int count;
        public List<String> sortList;
    }
}
src/main/java/com/taoke/autopay/dao/OrderTaskMapper.java
New file
@@ -0,0 +1,50 @@
package com.taoke.autopay.dao;
import java.lang.Integer;
import java.lang.String;
import java.util.Date;
import java.lang.Long;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.taoke.autopay.entity.js2.OrderTask;
import org.yeshi.utils.mybatis.BaseMapper;
public interface OrderTaskMapper extends BaseMapper<OrderTask> {
    OrderTask selectByPrimaryKeyForUpdate(@Param("id") Long id);
    List<OrderTask> list(@Param("query") DaoQuery query);
    List<OrderTask> listByIds(@Param("ids") List<Long> ids);
    long count(@Param("query") DaoQuery query);
    public static class DaoQuery{
        public Long id;
        public String keywordContent;
        public String keywordMd5;
        public Integer productSource;
        public String productName;
        public String shopName;
        public Integer requiredOrderCount;
        public Integer completedOrderCount;
        public Integer receivedOrderCount;
        public Integer reviewedOrderCount;
        public Integer receiveCycleHours;
        public Date orderStartTime;
        public Date orderEndTime;
        public Date minCreateTime;
        public Date maxCreateTime;
        public Date minUpdateTime;
        public Date maxUpdateTime;
        public Date minEstimatedReceiveTime;
        public Date maxEstimatedReceiveTime;
        public Integer status;
        public String statusDescription;
        public long start;
        public int count;
        public List<String> sortList;
    }
}
src/main/java/com/taoke/autopay/dao/credit/CreditExchangeRecordMapper.java
@@ -32,6 +32,7 @@
        public String exchangeInfo1;
        public String exchangeInfo2;
        public Integer exchangeStatus;
        public List<Integer> exchangeStatusList;
        public String exchangeStatusDescription;
        public Date minCreateTime;
        public Date maxCreateTime;
src/main/java/com/taoke/autopay/dto/admin/OrderExcelDataDto.java
@@ -33,6 +33,8 @@
    private String paySuccess;
    @ExcelProperty("状态简介")
    private String stateDesc;
    @ExcelProperty("订单状态")
    private String orderStateDesc;
    @ExcelProperty("订单金额")
    private String orderMoney;
    @ExcelProperty("支付时间")
src/main/java/com/taoke/autopay/dto/js2/ClientCountDTO.java
New file
@@ -0,0 +1,11 @@
package com.taoke.autopay.dto.js2;
import lombok.Data;
@Data
public class ClientCountDTO {
    private Long clientId;
    private Integer count;
}
src/main/java/com/taoke/autopay/entity/ClientAdditionalInfo.java
New file
@@ -0,0 +1,64 @@
package com.taoke.autopay.entity;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
import org.springframework.data.annotation.Id;
import org.yeshi.utils.generater.mybatis.Column;
import org.yeshi.utils.generater.mybatis.Table;
import java.util.Date;
/**
 * @author
 * @title: ClientAdditionalInfo
 * @description: 客户端附加信息实体类
 * @date 2024/6/14 18:09
 */
@Data
@Builder
@Table("table_client_additional_info")
public class ClientAdditionalInfo {
    @Tolerate
    public ClientAdditionalInfo(){
    }
    /**
     * 客户端ID(唯一ID)
     */
    @Id
    @Column(name = "client_id")
    private Long id;
    /**
     * 手机号
     */
    @Column(name = "mobile")
    private String mobile;
    /**
     * 支付宝账号
     */
    @Column(name = "alipay_account")
    private String alipayAccount;
    /**
     * 支付宝密码
     */
    @Column(name = "alipay_password")
    private String alipayPassword;
    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime;
    /**
     * 修改时间
     */
    @Column(name = "update_time")
    private Date updateTime;
}
src/main/java/com/taoke/autopay/entity/ClientInfo.java
@@ -1,5 +1,8 @@
package com.taoke.autopay.entity;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
import org.springframework.data.annotation.Id;
import org.yeshi.utils.generater.mybatis.Column;
import org.yeshi.utils.generater.mybatis.Table;
@@ -12,12 +15,26 @@
 * @description: TODO
 * @date 2024/6/14 18:09
 */
@Data
@Builder
@Table("table_user")
public class ClientInfo {
    // 管理员
    public static final int RULE_ADMIN = 1;
    //普通用户
    public static final int RULE_COMMON = 0;
    // 代付-默认值
    public static final int CLIENT_TYPE_AGENT_PAYMENT = 0;
    // 订单场景
    public static final int CLIENT_TYPE_ORDER = 1;
    @Tolerate
    public ClientInfo(){
    }
    @Id
    @Column(name ="id")
    private Long id;
@@ -33,61 +50,6 @@
    private Date activeTime;
    @Column(name ="rule")
    private Integer rule;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
    public Date getActiveTime() {
        return activeTime;
    }
    public void setActiveTime(Date activeTime) {
        this.activeTime = activeTime;
    }
    public Integer getRule() {
        return rule;
    }
    public void setRule(Integer rule) {
        this.rule = rule;
    }
    @Column(name = "client_type")
    private Integer clientType;
}
src/main/java/com/taoke/autopay/entity/js2/OrderTask.java
New file
@@ -0,0 +1,139 @@
package com.taoke.autopay.entity.js2;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.yeshi.utils.generater.mybatis.Column;
import org.yeshi.utils.generater.mybatis.Table;
import java.util.Date;
/**
 * @author
 * @title: OrderTask
 * @description: 下单任务实体类
 * @date 2025/7/28
 */
@Data
@Builder
@Table("table_order_task")
public class OrderTask {
    /**
     * 状态常量定义
     */
    /** 未分配 */
    public static final int STATUS_UNASSIGNED = 0;
    /** 分配中 */
    public static final int STATUS_ASSIGNING = 1;
    /** 已分配 */
    public static final int STATUS_ASSIGNED = 2;
    /**
     * 主键ID(自增)
     */
    @Id
    @Column(name = "id")
    private Long id;
    /**
     * 口令内容
     */
    @Column(name = "keyword_content")
    private String keywordContent;
    /**
     * 口令md5
     */
    @Column(name = "keyword_md5")
    private String keywordMd5;
    /**
     * 商品来源
     */
    @Column(name = "product_source")
    private Integer productSource;
    /**
     * 商品名称
     */
    @Column(name = "product_name")
    private String productName;
    /**
     * 店铺名称
     */
    @Column(name = "shop_name")
    private String shopName;
    /**
     * 任务所需下单数量
     */
    @Column(name = "required_order_count")
    private Integer requiredOrderCount;
    /**
     * 已经下单数量
     */
    @Column(name = "completed_order_count")
    private Integer completedOrderCount;
    /**
     * 已经确认收货的数量
     */
    @Column(name = "received_order_count")
    private Integer receivedOrderCount;
    /**
     * 已经评价的数量
     */
    @Column(name = "reviewed_order_count")
    private Integer reviewedOrderCount;
    /**
     * 确认收货周期(小时)
     */
    @Column(name = "receive_cycle_hours")
    private Integer receiveCycleHours;
    /**
     * 下单开始时间
     */
    @Column(name = "order_start_time")
    private Date orderStartTime;
    /**
     * 下单结束时间
     */
    @Column(name = "order_end_time")
    private Date orderEndTime;
    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime;
    /**
     * 更新时间
     */
    @Column(name = "update_time")
    private Date updateTime;
    /**
     * 预估确认收货时间
     */
    @Column(name = "estimated_receive_time")
    private Date estimatedReceiveTime;
    /**
     * 状态(int型)
     */
    @Column(name = "status")
    private Integer status;
    /**
     * 状态简介
     */
    @Column(name = "status_description")
    private String statusDescription;
}
src/main/java/com/taoke/autopay/entity/js2/OrderTaskExecutionDetail.java
New file
@@ -0,0 +1,125 @@
package com.taoke.autopay.entity.js2;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.yeshi.utils.generater.mybatis.Column;
import org.yeshi.utils.generater.mybatis.Table;
import java.util.Date;
/**
 * @author
 * @title: OrderTaskExecutionDetail
 * @description: 下单任务执行详情实体类
 * @date 2025/7/28
 */
@Data
@Builder
@Table("table_order_task_execution_detail")
public class OrderTaskExecutionDetail {
    /**
     * 执行状态常量定义
     */
    /** 未下单 */
    public static final int STATUS_NOT_ORDERED = 0;
    /** 已下单 */
    public static final int STATUS_ORDERED = 1;
    /** 下单失败 */
    public static final int STATUS_ORDER_FAILED = -1;
    /** 确认收货成功 */
    public static final int STATUS_RECEIVE_SUCCESS = 2;
    /** 确认收货失败 */
    public static final int STATUS_RECEIVE_FAILED = -2;
    /** 评价成功 */
    public static final int STATUS_REVIEW_SUCCESS = 3;
    /** 评价失败 */
    public static final int STATUS_REVIEW_FAILED = -3;
    /**
     * 主键(任务ID+设备ID)
     */
    @Id
    @Column(name = "id")
    private String id;
    /**
     * 任务ID
     */
    @Column(name = "task_id")
    private Long taskId;
    /**
     * 客户端ID
     */
    @Column(name = "client_id")
    private Long clientId;
    /**
     * 执行状态(int类型)
     */
    @Column(name = "execution_status")
    private Integer executionStatus;
    /**
     * 执行状态说明
     */
    @Column(name = "status_description")
    private String statusDescription;
    /**
     * 订单号
     */
    @Column(name = "order_no")
    private String orderNo;
    /**
     * 商品名称
     */
    @Column(name = "product_name")
    private String productName;
    /**
     * 店铺名称
     */
    @Column(name = "shop_name")
    private String shopName;
    /**
     * 券码
     */
    @Column(name = "coupon_code")
    private String couponCode;
    /**
     * 下单时间
     */
    @Column(name = "order_time")
    private Date orderTime;
    /**
     * 收货时间
     */
    @Column(name = "receive_time")
    private Date receiveTime;
    /**
     * 评价时间
     */
    @Column(name = "review_time")
    private Date reviewTime;
    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime;
    /**
     * 更新时间
     */
    @Column(name = "update_time")
    private Date updateTime;
}
src/main/java/com/taoke/autopay/exception/OrderTaskException.java
New file
@@ -0,0 +1,32 @@
package com.taoke.autopay.exception;
/**
 * @author
 * @title: OrderTaskException
 * @description: 下单任务异常类
 * @date 2025/7/28
 */
public class OrderTaskException extends Exception {
    public final static int CODE_COMMON = 1;
    public final static int CODE_TASK_NOT_FOUND = 10;
    public final static int CODE_TASK_CREATE_FAILED = 11;
    public final static int CODE_TASK_UPDATE_FAILED = 12;
    public final static int CODE_TASK_DELETE_FAILED = 13;
    private int code;
    public int getCode() {
        return code;
    }
    public OrderTaskException(int code, String msg) {
        super(msg);
        this.code = code;
    }
    public OrderTaskException(String msg) {
        super(msg);
        this.code = CODE_COMMON;
    }
}
src/main/java/com/taoke/autopay/exception/OrderTaskExecutionDetailException.java
New file
@@ -0,0 +1,32 @@
package com.taoke.autopay.exception;
/**
 * @author
 * @title: OrderTaskExecutionDetailException
 * @description: 下单任务执行详情异常类
 * @date 2025/7/28
 */
public class OrderTaskExecutionDetailException extends Exception {
    public final static int CODE_COMMON = 1;
    public final static int CODE_DETAIL_NOT_FOUND = 10;
    public final static int CODE_DETAIL_CREATE_FAILED = 11;
    public final static int CODE_DETAIL_UPDATE_FAILED = 12;
    public final static int CODE_DETAIL_DELETE_FAILED = 13;
    private int code;
    public int getCode() {
        return code;
    }
    public OrderTaskExecutionDetailException(int code, String msg) {
        super(msg);
        this.code = code;
    }
    public OrderTaskExecutionDetailException(String msg) {
        super(msg);
        this.code = CODE_COMMON;
    }
}
src/main/java/com/taoke/autopay/factory/OrderFactory.java
@@ -67,7 +67,7 @@
                .build();
    }
    private static String getOrderStateDesc(Integer orderState, Integer orderType) {
    public static String getOrderStateDesc(Integer orderState, Integer orderType) {
        if (orderState == null || orderType == null) {
            return "未知";
src/main/java/com/taoke/autopay/manager/UserCreditExchangeManager.java
@@ -14,6 +14,7 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -213,7 +214,8 @@
     */
    public BigDecimal calculateExchangeAmount(Long uid, int credit, boolean forExchange) throws UserCreditExchangeException {
        long count = userCreditExchangeRecordService.countExchangeRecords(CreditExchangeRecordMapper.DaoQuery.builder()
                .uid(uid).build());
                        .exchangeStatusList(Arrays.asList(new Integer[]{CreditExchangeRecord.STATUS_PASSED, CreditExchangeRecord.STATUS_NOT_VERIFY}))
                        .uid(uid).build());
        Date nowDate = new Date(TimeUtil.convertToTimeTemp(TimeUtil.getGernalTime(System.currentTimeMillis(), "yyyyMMddHHmm"), "yyyyMMddHHmm"));
        BigDecimal money = null;
src/main/java/com/taoke/autopay/service/ClientAdditionalInfoService.java
New file
@@ -0,0 +1,41 @@
package com.taoke.autopay.service;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import java.util.List;
import java.util.Map;
public interface ClientAdditionalInfoService {
    /**
     * 增加客户端附加信息
     *
     * @param clientAdditionalInfo 客户端附加信息
     * @return 是否添加成功
     */
    boolean addClientAdditionalInfo(ClientAdditionalInfo clientAdditionalInfo);
    /**
     * 更新客户端附加信息
     *
     * @param clientAdditionalInfo 客户端附加信息
     * @return 是否更新成功
     */
    boolean updateClientAdditionalInfo(ClientAdditionalInfo clientAdditionalInfo);
    /**
     * 根据设备ID列表获取信息的map
     *
     * @param clientIds 设备ID列表
     * @return 客户端附加信息映射Map,key为客户端ID,value为客户端附加信息
     */
    Map<Long, ClientAdditionalInfo> getClientAdditionalInfoMap(List<Long> clientIds);
    /**
     * 根据设备ID获取信息
     *
     * @param clientId 设备ID
     * @return 客户端附加信息
     */
    ClientAdditionalInfo getClientAdditionalInfoByClientId(Long clientId);
}
src/main/java/com/taoke/autopay/service/ClientInfoService.java
@@ -92,4 +92,10 @@
    public List<Long> getRePayClientIds();
    /**
     * 获取可用的下单设备
     * @return
     */
    public List<ClientInfo> getAvailableClientsForOrder();
}
src/main/java/com/taoke/autopay/service/impl/ClientAdditionalInfoServiceImpl.java
New file
@@ -0,0 +1,92 @@
package com.taoke.autopay.service.impl;
import com.taoke.autopay.dao.ClientAdditionalInfoMapper;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import com.taoke.autopay.entity.SystemConfigKeyEnum;
import com.taoke.autopay.exception.LoginException;
import com.taoke.autopay.service.ClientAdditionalInfoService;
import com.taoke.autopay.service.SystemConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * 客户端附加信息服务
 */
@Service
public class ClientAdditionalInfoServiceImpl implements ClientAdditionalInfoService {
    @Resource
    private ClientAdditionalInfoMapper clientAdditionalInfoMapper;
    @Resource
    private SystemConfigService systemConfigService;
    /**
     * 增加客户端附加信息
     *
     * @param clientAdditionalInfo 客户端附加信息
     * @return 是否添加成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addClientAdditionalInfo(ClientAdditionalInfo clientAdditionalInfo) {
        if (clientAdditionalInfo == null) {
            return false;
        }
        clientAdditionalInfo.setCreateTime(new Date());
        clientAdditionalInfo.setUpdateTime(new Date());
        return clientAdditionalInfoMapper.insert(clientAdditionalInfo) > 0;
    }
    /**
     * 更新客户端附加信息
     *
     * @param clientAdditionalInfo 客户端附加信息
     * @return 是否更新成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateClientAdditionalInfo(ClientAdditionalInfo clientAdditionalInfo) {
        if (clientAdditionalInfo == null || clientAdditionalInfo.getId() == null) {
            return false;
        }
        clientAdditionalInfo.setUpdateTime(new Date());
        return clientAdditionalInfoMapper.updateByPrimaryKey(clientAdditionalInfo) > 0;
    }
    /**
     * 根据设备ID列表获取信息的map
     *
     * @param clientIds 设备ID列表
     * @return 客户端附加信息映射Map,key为客户端ID,value为客户端附加信息
     */
    @Override
    public Map<Long, ClientAdditionalInfo> getClientAdditionalInfoMap(List<Long> clientIds) {
        if (clientIds == null || clientIds.isEmpty()) {
            return new HashMap<>();
        }
        List<ClientAdditionalInfo> clientAdditionalInfos = clientAdditionalInfoMapper.listByIds(clientIds);
        return clientAdditionalInfos.stream()
                .collect(Collectors.toMap(ClientAdditionalInfo::getId, info -> info));
    }
    /**
     * 根据设备ID获取信息
     *
     * @param clientId 设备ID
     * @return 客户端附加信息
     */
    @Override
    public ClientAdditionalInfo getClientAdditionalInfoByClientId(Long clientId) {
        if (clientId == null) {
            return null;
        }
        return clientAdditionalInfoMapper.selectByPrimaryKey(clientId);
    }
}
src/main/java/com/taoke/autopay/service/impl/ClientInfoServiceImpl.java
@@ -1,9 +1,11 @@
package com.taoke.autopay.service.impl;
import com.taoke.autopay.dao.ClientInfoMapper;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import com.taoke.autopay.entity.ClientInfo;
import com.taoke.autopay.entity.SystemConfigKeyEnum;
import com.taoke.autopay.exception.LoginException;
import com.taoke.autopay.service.ClientAdditionalInfoService;
import com.taoke.autopay.service.ClientInfoService;
import com.taoke.autopay.service.SystemConfigService;
import com.taoke.autopay.utils.StringUtil;
@@ -11,10 +13,7 @@
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.*;
/**
 * @author hxh
@@ -29,6 +28,9 @@
    @Resource
    private SystemConfigService systemConfigService;
    @Resource
    private ClientAdditionalInfoService clientAdditionalInfoService;
    @Override
    public ClientInfo login(String account, String pwd) throws LoginException {
@@ -59,9 +61,7 @@
    @Override
    public void setActiveTime(Long id, Date date) {
        ClientInfo info = new ClientInfo();
        info.setId(id);
        info.setActiveTime(date);
        ClientInfo info = ClientInfo.builder().id(id).activeTime(date).build();
        clientInfoMapper.updateByPrimaryKeySelective(info);
    }
@@ -97,27 +97,33 @@
            // 查询最大的账号
            ClientInfoMapper.DaoQuery query = new ClientInfoMapper.DaoQuery();
            query.sortList = Arrays.asList(new String[]{"id desc"});
            query.clientType = info.getClientType();
            query.count = 1;
            List<ClientInfo> list = list(query);
            long maxId = 0;
            if (list.size() > 0) {
                maxId = list.get(0).getId();
            }
            info.setAccount("c" + maxId);
            if(info.getClientType()==ClientInfo.CLIENT_TYPE_AGENT_PAYMENT) {
                info.setAccount("c" + maxId);
            }else if(info.getClientType()==ClientInfo.CLIENT_TYPE_ORDER){
                info.setAccount("s" + maxId);
            }
        }
        clientInfoMapper.insertSelective(info);
        ClientInfo update = new ClientInfo();
        update.setId(info.getId());
        update.setName("客户端" + (info.getId() - 1));
        ClientInfo update =  ClientInfo.builder()
                .id(info.getId())
                .name("客户端" + (info.getId() - 1))
                .build();
        clientInfoMapper.updateByPrimaryKeySelective(update);
    }
    @Override
    public void setPwd(Long id, String pwd) {
        ClientInfo update = new ClientInfo();
        update.setId(id);
        update.setPwd(pwd);
        ClientInfo update = ClientInfo.builder()
                .id( id)
                .pwd(pwd)
                .build();
        clientInfoMapper.updateByPrimaryKeySelective(update);
    }
@@ -135,4 +141,36 @@
        }
        return idList;
    }
    @Override
    public List<ClientInfo> getAvailableClientsForOrder() {
        ClientInfoMapper.DaoQuery query = new ClientInfoMapper.DaoQuery();
        query.clientType = ClientInfo.CLIENT_TYPE_ORDER;
        query.count = Integer.MAX_VALUE;
        query.minActiveTime = new Date(System.currentTimeMillis()-1000*60*30);// 30分钟内活跃的设备
        List<ClientInfo> clientInfoList = list(query);
        if(clientInfoList.isEmpty()){
            return clientInfoList;
        }
        // 获取电话号码,支付宝账号,支付宝密码
        List<Long> clientIds=new ArrayList<>();
        clientInfoList.forEach(clientInfo -> {
            clientIds.add(clientInfo.getId());
        });
        Map<Long, ClientAdditionalInfo> map = clientAdditionalInfoService.getClientAdditionalInfoMap(clientIds);
        for(int i=0;i<clientInfoList.size();i++){
            ClientAdditionalInfo info =   map.get(clientInfoList.get(i).getId());
            if(info==null){
                clientInfoList.remove(i);
                i--;
                continue;
            }
            if(StringUtil.isNullOrEmpty(info.getMobile())||StringUtil.isNullOrEmpty(info.getAlipayAccount())||StringUtil.isNullOrEmpty(info.getAlipayPassword())){
                clientInfoList.remove(i);
                i--;
                continue;
            }
        }
        return clientInfoList;
    }
}
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskExecutionDetailServiceImpl.java
New file
@@ -0,0 +1,384 @@
package com.taoke.autopay.service.impl.js2;
import com.taoke.autopay.dao.OrderTaskExecutionDetailMapper;
import com.taoke.autopay.dao.OrderTaskMapper;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.exception.OrderTaskExecutionDetailException;
import com.taoke.autopay.service.js2.OrderTaskExecutionDetailService;
import com.taoke.autopay.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
/**
 * @author
 * @title: OrderTaskExecutionDetailServiceImpl
 * @description: 下单任务执行详情服务实现类
 * @date 2025/7/28
 */
@Service
public class OrderTaskExecutionDetailServiceImpl implements OrderTaskExecutionDetailService {
    @Resource
    private OrderTaskExecutionDetailMapper orderTaskExecutionDetailMapper;
    private static final Logger logger = LoggerFactory.getLogger(OrderTaskExecutionDetailServiceImpl.class);
    @Autowired
    private OrderTaskMapper orderTaskMapper;
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderTaskExecutionDetail createOrderTaskExecutionDetail(OrderTaskExecutionDetail orderTaskExecutionDetail) throws OrderTaskExecutionDetailException {
        validateOrderTaskExecutionDetail(orderTaskExecutionDetail);
        // 设置创建时间
        if (orderTaskExecutionDetail.getCreateTime() == null) {
            orderTaskExecutionDetail.setCreateTime(new Date());
        }
        // 设置更新时间
        orderTaskExecutionDetail.setUpdateTime(new Date());
        // 插入数据库
        orderTaskExecutionDetailMapper.insertSelective(orderTaskExecutionDetail);
        return orderTaskExecutionDetail;
    }
    @Override
    public OrderTaskExecutionDetail getOrderTaskExecutionDetailById(String id) {
        if (id == null) {
            return null;
        }
        return orderTaskExecutionDetailMapper.selectByPrimaryKey(id);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderTaskExecutionDetail getOrderTaskExecutionDetailByIdForUpdate(String id) {
        if (id == null) {
            return null;
        }
        return orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateOrderTaskExecutionDetail(OrderTaskExecutionDetail orderTaskExecutionDetail) throws OrderTaskExecutionDetailException {
        if (orderTaskExecutionDetail == null || orderTaskExecutionDetail.getId() == null) {
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "下单任务执行详情或详情ID不能为空");
        }
        // 设置更新时间
        orderTaskExecutionDetail.setUpdateTime(new Date());
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(orderTaskExecutionDetail);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteOrderTaskExecutionDetail(String id) throws OrderTaskExecutionDetailException {
        if (id == null) {
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        // 删除数据库记录
        orderTaskExecutionDetailMapper.deleteByPrimaryKey(id);
    }
    @Override
    public List<OrderTaskExecutionDetail> listOrderTaskExecutionDetails(OrderTaskExecutionDetailMapper.DaoQuery query, int page, int pageSize) {
        if (query == null) {
            query = new OrderTaskExecutionDetailMapper.DaoQuery();
        }
        query.start = (long) (page - 1) * pageSize;
        query.count = pageSize;
        return orderTaskExecutionDetailMapper.list(query);
    }
    @Override
    public long countOrderTaskExecutionDetails(OrderTaskExecutionDetailMapper.DaoQuery query) {
        if (query == null) {
            query = new OrderTaskExecutionDetailMapper.DaoQuery();
        }
        return orderTaskExecutionDetailMapper.count(query);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void orderSuccess(String id, String orderNo, String productTitle, String shopName, Date orderTime) throws OrderTaskExecutionDetailException {
        if (id == null) {
            logger.error("任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        // 获取任务执行详情(带锁)
        OrderTaskExecutionDetail detail = orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
        if (detail == null) {
            logger.error("未找到ID为{}的任务执行详情", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_DETAIL_NOT_FOUND, "未找到对应的任务执行详情");
        }
        if(detail.getExecutionStatus()!=OrderTaskExecutionDetail.STATUS_NOT_ORDERED){
            logger.error("尚未处于未下单状态:{}", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "尚未处于未下单状态");
        }
        OrderTaskExecutionDetail update=OrderTaskExecutionDetail.builder()
                .id(id)
                .orderNo(orderNo)
                .productName(productTitle)
                .shopName(shopName)
                .orderTime(orderTime)
                .executionStatus(OrderTaskExecutionDetail.STATUS_ORDERED)
                .statusDescription("下单成功")
                .updateTime(new Date())
                .build();
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(update);
        // 更新任务下单成功数量
        OrderTask orderTask = orderTaskMapper.selectByPrimaryKeyForUpdate(detail.getTaskId());
        orderTaskMapper.updateByPrimaryKeySelective( OrderTask.builder()
                .id(orderTask.getId())
                .updateTime(new Date())
                .completedOrderCount(orderTask.getCompletedOrderCount() + 1)
                .build());
        logger.info("任务执行详情ID:{} 下单成功,订单号:{}", id, orderNo);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void orderFailure(String id, String failureReason) throws OrderTaskExecutionDetailException {
        if (id == null) {
            logger.error("任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        if (StringUtil.isNullOrEmpty(failureReason)) {
            logger.warn("下单失败原因为空,使用默认原因");
            failureReason = "未知错误";
        }
        // 获取任务执行详情(带锁)
        OrderTaskExecutionDetail detail = orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
        if (detail == null) {
            logger.error("未找到ID为{}的任务执行详情", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_DETAIL_NOT_FOUND, "未找到对应的任务执行详情");
        }
        if(detail.getExecutionStatus()!=OrderTaskExecutionDetail.STATUS_NOT_ORDERED){
            logger.error("尚未处于未下单状态:{}", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "尚未处于未下单状态");
        }
        OrderTaskExecutionDetail update=OrderTaskExecutionDetail.builder()
                .id(id)
                .executionStatus(OrderTaskExecutionDetail.STATUS_ORDER_FAILED)
                .statusDescription("下单失败:" + failureReason)
                .updateTime(new Date())
                .build();
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(update);
        logger.info("任务执行详情ID:{} 下单失败,原因:{}", id, failureReason);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void confirmReceiptSuccess(String id, String couponCode) throws OrderTaskExecutionDetailException {
        if (id == null) {
            logger.error("任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        // 获取任务执行详情(带锁)
        OrderTaskExecutionDetail detail = orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
        if (detail == null) {
            logger.error("未找到ID为{}的任务执行详情", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_DETAIL_NOT_FOUND, "未找到对应的任务执行详情");
        }
        if(detail.getExecutionStatus()!=OrderTaskExecutionDetail.STATUS_ORDERED){
            logger.error("尚未处于下单状态:{}", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "尚未处于下单状态");
        }
        // 更新字段
        OrderTaskExecutionDetail update = OrderTaskExecutionDetail.builder()
                .id(id)
                .couponCode(couponCode)
                .receiveTime(new Date())
                .executionStatus(OrderTaskExecutionDetail.STATUS_RECEIVE_SUCCESS)
                .statusDescription("确认收货成功")
                .updateTime(new Date())
                .build();
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(update);
        // 更新任务确认收货成功数量
        OrderTask orderTask = orderTaskMapper.selectByPrimaryKeyForUpdate(detail.getTaskId());
        orderTaskMapper.updateByPrimaryKeySelective( OrderTask.builder()
                .id(orderTask.getId())
                .updateTime(new Date())
                .receivedOrderCount(orderTask.getReceivedOrderCount() + 1)
                .build());
        logger.info("任务执行详情ID:{} 确认收货成功,券码:{}", id, couponCode);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void confirmReceiptFailure(String id, String reason) throws OrderTaskExecutionDetailException {
        if (id == null) {
            logger.error("任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        if (StringUtil.isNullOrEmpty(reason)) {
            logger.warn("确认收货失败原因为空,使用默认原因");
            reason = "未知错误";
        }
        // 获取任务执行详情(带锁)
        OrderTaskExecutionDetail detail = orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
        if (detail == null) {
            logger.error("未找到ID为{}的任务执行详情", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_DETAIL_NOT_FOUND, "未找到对应的任务执行详情");
        }
        if(detail.getExecutionStatus()!=OrderTaskExecutionDetail.STATUS_ORDERED){
            logger.error("尚未处于下单状态:{}", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "尚未处于下单状态");
        }
        // 更新字段
        detail.setExecutionStatus(OrderTaskExecutionDetail.STATUS_RECEIVE_FAILED); // 设置为确认收货失败状态
        detail.setStatusDescription("确认收货失败:" + reason);
        // 更新时间
        detail.setUpdateTime(new Date());
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(detail);
        logger.info("任务执行详情ID:{} 确认收货失败,原因:{}", id, reason);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void reviewSuccess(String id) throws OrderTaskExecutionDetailException {
        if (id == null) {
            logger.error("任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        // 获取任务执行详情(带锁)
        OrderTaskExecutionDetail detail = orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
        if (detail == null) {
            logger.error("未找到ID为{}的任务执行详情", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_DETAIL_NOT_FOUND, "未找到对应的任务执行详情");
        }
        if(detail.getExecutionStatus()!=OrderTaskExecutionDetail.STATUS_RECEIVE_SUCCESS){
            logger.error("尚未处于确认收货状态:{}", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "尚未处于确认收货状态");
        }
        // 更新字段
        OrderTaskExecutionDetail update = OrderTaskExecutionDetail.builder()
                .id(id)
                .reviewTime(new Date())
                .executionStatus(OrderTaskExecutionDetail.STATUS_REVIEW_SUCCESS)
                .statusDescription("评价成功")
                .updateTime(new Date())
                .build();
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(update);
        // 更新任务评价成功数量
        OrderTask orderTask = orderTaskMapper.selectByPrimaryKeyForUpdate(detail.getTaskId());
        orderTaskMapper.updateByPrimaryKeySelective( OrderTask.builder()
                .id(orderTask.getId())
                .updateTime(new Date())
                .reviewedOrderCount(orderTask.getReviewedOrderCount() + 1)
                .build());
        logger.info("任务执行详情ID:{} 评价成功", id);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void reviewFailure(String id, String reason) throws OrderTaskExecutionDetailException {
        if (id == null) {
            logger.error("任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务执行详情ID不能为空");
        }
        if (StringUtil.isNullOrEmpty(reason)) {
            logger.warn("评价失败原因为空,使用默认原因");
            reason = "未知错误";
        }
        // 获取任务执行详情(带锁)
        OrderTaskExecutionDetail detail = orderTaskExecutionDetailMapper.selectByPrimaryKeyForUpdate(id);
        if (detail == null) {
            logger.error("未找到ID为{}的任务执行详情", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_DETAIL_NOT_FOUND, "未找到对应的任务执行详情");
        }
        if(detail.getExecutionStatus()!=OrderTaskExecutionDetail.STATUS_RECEIVE_SUCCESS){
            logger.error("尚未处于确认收货状态:{}", id);
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "尚未处于确认收货状态");
        }
        // 更新字段
        detail.setExecutionStatus(OrderTaskExecutionDetail.STATUS_REVIEW_FAILED); // 设置为评价失败状态
        detail.setStatusDescription("评价失败:" + reason);
        // 更新时间
        detail.setUpdateTime(new Date());
        // 更新数据库
        orderTaskExecutionDetailMapper.updateByPrimaryKeySelective(detail);
        logger.info("任务执行详情ID:{} 评价失败,原因:{}", id, reason);
    }
    /**
     * 验证订单任务执行详情的必填字段
     *
     * @param orderTaskExecutionDetail 订单任务执行详情对象
     * @throws OrderTaskExecutionDetailException 如果必填字段为空则抛出异常
     */
    private void validateOrderTaskExecutionDetail(OrderTaskExecutionDetail orderTaskExecutionDetail) throws OrderTaskExecutionDetailException {
        if (orderTaskExecutionDetail == null) {
            logger.error("下单任务执行详情不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "下单任务执行详情不能为空");
        }
        if (StringUtil.isNullOrEmpty(orderTaskExecutionDetail.getId())) {
            logger.error("下单任务执行详情ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "下单任务执行详情ID不能为空");
        }
        if (orderTaskExecutionDetail.getTaskId() == null) {
            logger.error("任务ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "任务ID不能为空");
        }
        if (orderTaskExecutionDetail.getClientId() == null) {
            logger.error("客户端ID不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "客户端ID不能为空");
        }
        if (orderTaskExecutionDetail.getExecutionStatus() == null) {
            logger.error("执行状态不能为空");
            throw new OrderTaskExecutionDetailException(OrderTaskExecutionDetailException.CODE_COMMON, "执行状态不能为空");
        }
    }
}
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskServiceImpl.java
New file
@@ -0,0 +1,263 @@
package com.taoke.autopay.service.impl.js2;
import com.taoke.autopay.dao.OrderTaskExecutionDetailMapper;
import com.taoke.autopay.dao.OrderTaskMapper;
import com.taoke.autopay.dto.js2.ClientCountDTO;
import com.taoke.autopay.entity.ClientInfo;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.exception.OrderTaskException;
import com.taoke.autopay.service.ClientInfoService;
import com.taoke.autopay.service.js2.OrderTaskExecutionDetailService;
import com.taoke.autopay.service.js2.OrderTaskService;
import com.taoke.autopay.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
/**
 * @author
 * @title: OrderTaskServiceImpl
 * @description: 下单任务服务实现类
 * @date 2025/7/28
 */
@Service
public class OrderTaskServiceImpl implements OrderTaskService {
    @Resource
    private OrderTaskMapper orderTaskMapper;
    @Resource
    private OrderTaskExecutionDetailMapper orderTaskExecutionDetailMapper;
    @Resource
    private ClientInfoService clientInfoService;
    @Autowired
    private OrderTaskExecutionDetailService orderTaskExecutionDetailService;
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderTask createOrderTask(OrderTask orderTask) throws OrderTaskException {
        // 参数校验
        if (orderTask == null) {
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "下单任务不能为空");
        }
        if(StringUtil.isNullOrEmpty(orderTask.getKeywordContent())){
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "下单任务口令不能为空");
        }
        if(orderTask.getProductSource()==null){
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "下单任务商品来源不能为空");
        }
        if(orderTask.getRequiredOrderCount()==null){
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "下单任务需求下单数量不能为空");
        }
        if(orderTask.getReceiveCycleHours()==null){
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "下单任务收货周期不能为空");
        }
        if(orderTask.getOrderStartTime()==null){
            orderTask.setOrderStartTime(new Date());
        }
        if(orderTask.getOrderEndTime()==null){
            orderTask.setOrderEndTime(new Date(System.currentTimeMillis()+1000*60*60*24L));
        }
        orderTask.setCompletedOrderCount(0);
        orderTask.setReceivedOrderCount(0);
        orderTask.setReviewedOrderCount(0);
        // 设置创建时间
        if (orderTask.getCreateTime() == null) {
            orderTask.setCreateTime(new Date());
        }
        // 设置更新时间
        orderTask.setUpdateTime(new Date());
        // 插入数据库
        orderTaskMapper.insertSelective(orderTask);
        return orderTask;
    }
    @Override
    public OrderTask getOrderTaskById(Long id) {
        if (id == null) {
            return null;
        }
        return orderTaskMapper.selectByPrimaryKey(id);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderTask getOrderTaskByIdForUpdate(Long id) {
        if (id == null) {
            return null;
        }
        return orderTaskMapper.selectByPrimaryKeyForUpdate(id);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateOrderTask(OrderTask orderTask) throws OrderTaskException {
        // 参数校验
        if (orderTask == null || orderTask.getId() == null) {
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "下单任务或任务ID不能为空");
        }
        // 设置更新时间
        orderTask.setUpdateTime(new Date());
        // 更新数据库
        orderTaskMapper.updateByPrimaryKeySelective(orderTask);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteOrderTask(Long id) throws OrderTaskException {
        if (id == null) {
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "任务ID不能为空");
        }
        // 删除数据库记录
        orderTaskMapper.deleteByPrimaryKey(id);
    }
    @Override
    public List<OrderTask> listOrderTasks(OrderTaskMapper.DaoQuery query, int page, int pageSize) {
        if (query == null) {
            query = new OrderTaskMapper.DaoQuery();
        }
        query.start = (long) (page - 1) * pageSize;
        query.count = pageSize;
        return orderTaskMapper.list(query);
    }
    @Override
    public long countOrderTasks(OrderTaskMapper.DaoQuery query) {
        if (query == null) {
            query = new OrderTaskMapper.DaoQuery();
        }
        return orderTaskMapper.count(query);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int assignTask(Long taskId) throws OrderTaskException {
        // 如果任务id对应的任务不存在,要抛出异常
        if (taskId == null) {
            throw new OrderTaskException(OrderTaskException.CODE_COMMON, "任务ID不能为空");
        }
        OrderTask orderTask = orderTaskMapper.selectByPrimaryKeyForUpdate(taskId);
        if (orderTask == null) {
            throw new OrderTaskException(OrderTaskException.CODE_TASK_NOT_FOUND, "任务不存在");
        }
        // 如果任务状态是已分配状态,返回状态
        if (orderTask.getStatus() != null && orderTask.getStatus() == OrderTask.STATUS_ASSIGNED) {
            return orderTask.getStatus();
        }
        // 通过查询OrderTaskExecutionDetail获取已经分配的客户端数量与客户端id;
        OrderTaskExecutionDetailMapper.DaoQuery query = new OrderTaskExecutionDetailMapper.DaoQuery();
        query.taskId = taskId;
        query.count = Integer.MAX_VALUE;
        List<OrderTaskExecutionDetail> executionDetails = orderTaskExecutionDetailMapper.list(query);
        // 得到剩余的客户端数量
        int assignedClientCount = executionDetails.size();
        int requiredOrderCount = orderTask.getRequiredOrderCount() != null ? orderTask.getRequiredOrderCount() : 0;
        int remainingClientCount = requiredOrderCount - assignedClientCount;
        if(remainingClientCount<=0){
            if(orderTask.getStatus() != OrderTask.STATUS_ASSIGNED){
                orderTask.setStatus(OrderTask.STATUS_ASSIGNED);
                orderTaskMapper.updateByPrimaryKeySelective(OrderTask.builder()
                                .id(orderTask.getId())
                                .status(OrderTask.STATUS_ASSIGNED)
                                .updateTime(new Date())
                        .build());
            }
            return OrderTask.STATUS_ASSIGNED;
        }
        // 获取可分配的设备
        List<ClientInfo> clientInfoList =  clientInfoService.getAvailableClientsForOrder();
        // 剔除已经分配了的设备
        Set<Long> excludeClientIds=new HashSet<>();
        executionDetails.forEach(detail -> {
            excludeClientIds.add( detail.getClientId());
        });
        for(int i=0;i<clientInfoList.size();i++){
            if (excludeClientIds.contains(clientInfoList.get(i).getId())){
                clientInfoList.remove(i);
                i--;
            }
        }
        // 统计所有设备正在执行任务的数量
        List<ClientCountDTO> clientCountList =   orderTaskExecutionDetailMapper.statisticClientIdsCountByStatus(Arrays.asList( new Integer[]{OrderTaskExecutionDetail.STATUS_NOT_ORDERED}));
        Map<Long,  Integer> clientCountMap = new HashMap<>();
        for(ClientCountDTO dto:clientCountList){
            clientCountMap.put(dto.getClientId(), dto.getCount());
        }
        clientInfoList.sort(new Comparator<ClientInfo>() {
            @Override
            public int compare(ClientInfo o1, ClientInfo o2) {
                int count1 = clientCountMap.get(o1.getId())==null?0:clientCountMap.get(o1.getId());
                int count2 = clientCountMap.get(o2.getId())==null?0:clientCountMap.get(o2.getId());
                if(count1!=count2){
                    return count1-count2;
                }
                // 根据活跃时间排序
                long time1 = o1.getActiveTime()==null?0:o1.getActiveTime().getTime();
                long time2 = o2.getActiveTime()==null?0:o2.getActiveTime().getTime();
                return  time2-time1 > 0 ? 1:-1;
            }
        });
        if(clientInfoList.size()>remainingClientCount){
            clientInfoList = clientInfoList.subList(0, remainingClientCount);
        }
        // 分配任务
        for(ClientInfo clientInfo:clientInfoList) {
            OrderTaskExecutionDetail detail = OrderTaskExecutionDetail.builder()
                    .id(taskId + "-" + clientInfo.getId())
                    .taskId(taskId)
                    .clientId(clientInfo.getId())
                    .executionStatus(OrderTaskExecutionDetail.STATUS_NOT_ORDERED)
                    .createTime(new Date())
                    .statusDescription("未下单")
                    .build();
            orderTaskExecutionDetailMapper.insertSelective(detail);
        }
        // 统计已经分配的客户端数量
        OrderTaskExecutionDetailMapper.DaoQuery daoQuery=new OrderTaskExecutionDetailMapper.DaoQuery();
        daoQuery.taskId = taskId;
        long assignedCount = orderTaskExecutionDetailService.countOrderTaskExecutionDetails(daoQuery);
        if(orderTask.getRequiredOrderCount()<=assignedCount){
          // 更新状态
           orderTaskMapper.updateByPrimaryKeySelective( OrderTask.builder().id(taskId)
                    .updateTime(new Date())
                    .statusDescription("已分配")
                    .status(OrderTask.STATUS_ASSIGNED).build());
        }else{
            orderTaskMapper.updateByPrimaryKeySelective( OrderTask.builder().id(taskId)
                    .updateTime(new Date())
                    .statusDescription("分配中")
                    .status(OrderTask.STATUS_ASSIGNING).build());
        }
        return orderTask.getStatus();
    }
}
src/main/java/com/taoke/autopay/service/js2/OrderTaskExecutionDetailService.java
New file
@@ -0,0 +1,132 @@
package com.taoke.autopay.service.js2;
import com.taoke.autopay.dao.OrderTaskExecutionDetailMapper;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.exception.OrderTaskExecutionDetailException;
import java.util.Date;
import java.util.List;
/**
 * @author
 * @title: OrderTaskExecutionDetailService
 * @description: 下单任务执行详情服务接口
 * @date 2025/7/28
 */
public interface OrderTaskExecutionDetailService {
    /**
     * 创建下单任务执行详情
     *
     * @param orderTaskExecutionDetail 下单任务执行详情实体
     * @return 创建后的任务执行详情实体
     * @throws OrderTaskExecutionDetailException 任务执行详情创建异常
     */
    OrderTaskExecutionDetail createOrderTaskExecutionDetail(OrderTaskExecutionDetail orderTaskExecutionDetail) throws OrderTaskExecutionDetailException;
    /**
     * 根据ID查询下单任务执行详情
     *
     * @param id 任务执行详情ID
     * @return 下单任务执行详情实体
     */
    OrderTaskExecutionDetail getOrderTaskExecutionDetailById(String id);
    /**
     * 根据ID更新下单任务执行详情(带锁)
     *
     * @param id 任务执行详情ID
     * @return 下单任务执行详情实体
     */
    OrderTaskExecutionDetail getOrderTaskExecutionDetailByIdForUpdate(String id);
    /**
     * 更新下单任务执行详情
     *
     * @param orderTaskExecutionDetail 下单任务执行详情实体
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void updateOrderTaskExecutionDetail(OrderTaskExecutionDetail orderTaskExecutionDetail) throws OrderTaskExecutionDetailException;
    /**
     * 删除下单任务执行详情
     *
     * @param id 任务执行详情ID
     * @throws OrderTaskExecutionDetailException 任务执行详情删除异常
     */
    void deleteOrderTaskExecutionDetail(String id) throws OrderTaskExecutionDetailException;
    /**
     * 查询下单任务执行详情列表
     *
     * @param query 查询条件
     * @param page 页码
     * @param pageSize 每页数量
     * @return 下单任务执行详情列表
     */
    List<OrderTaskExecutionDetail> listOrderTaskExecutionDetails(OrderTaskExecutionDetailMapper.DaoQuery query, int page, int pageSize);
    /**
     * 统计下单任务执行详情数量
     *
     * @param query 查询条件
     * @return 任务执行详情数量
     */
    long countOrderTaskExecutionDetails(OrderTaskExecutionDetailMapper.DaoQuery query);
    /**
     * 下单成功
     *
     * @param id 任务执行详情ID
     * @param orderNo 订单号
     * @param productTitle 商品标题
     * @param shopName 店铺名称
     * @param orderTime 下单时间
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void orderSuccess(String id, String orderNo, String productTitle, String shopName, Date orderTime) throws OrderTaskExecutionDetailException;
    /**
     * 下单失败
     *
     * @param id 任务执行详情ID
     * @param failureReason 失败原因
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void orderFailure(String id, String failureReason) throws OrderTaskExecutionDetailException;
    /**
     * 确认收货成功
     *
     * @param id 任务执行详情ID
     * @param couponCode 券码
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void confirmReceiptSuccess(String id, String couponCode) throws OrderTaskExecutionDetailException;
    /**
     * 确认收货失败
     *
     * @param id 任务执行详情ID
     * @param reason 失败原因
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void confirmReceiptFailure(String id, String reason) throws OrderTaskExecutionDetailException;
    /**
     * 评价成功
     *
     * @param id 任务执行详情ID
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void reviewSuccess(String id) throws OrderTaskExecutionDetailException;
    /**
     * 评价失败
     *
     * @param id 任务执行详情ID
     * @param reason 失败原因
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void reviewFailure(String id, String reason) throws OrderTaskExecutionDetailException;
}
src/main/java/com/taoke/autopay/service/js2/OrderTaskService.java
New file
@@ -0,0 +1,84 @@
package com.taoke.autopay.service.js2;
import com.taoke.autopay.dao.OrderTaskMapper;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.exception.OrderTaskException;
import java.util.List;
/**
 * @author
 * @title: OrderTaskService
 * @description: 下单任务服务接口
 * @date 2025/7/28
 */
public interface OrderTaskService {
    /**
     * 创建下单任务
     *
     * @param orderTask 下单任务实体
     * @return 创建后的任务实体
     * @throws OrderTaskException 任务创建异常
     */
    OrderTask createOrderTask(OrderTask orderTask) throws OrderTaskException;
    /**
     * 根据ID查询下单任务
     *
     * @param id 任务ID
     * @return 下单任务实体
     */
    OrderTask getOrderTaskById(Long id);
    /**
     * 根据ID更新下单任务(带锁)
     *
     * @param id 任务ID
     * @return 下单任务实体
     */
    OrderTask getOrderTaskByIdForUpdate(Long id);
    /**
     * 更新下单任务
     *
     * @param orderTask 下单任务实体
     * @throws OrderTaskException 任务更新异常
     */
    void updateOrderTask(OrderTask orderTask) throws OrderTaskException;
    /**
     * 删除下单任务
     *
     * @param id 任务ID
     * @throws OrderTaskException 任务删除异常
     */
    void deleteOrderTask(Long id) throws OrderTaskException;
    /**
     * 查询下单任务列表
     *
     * @param query 查询条件
     * @param page 页码
     * @param pageSize 每页数量
     * @return 下单任务列表
     */
    List<OrderTask> listOrderTasks(OrderTaskMapper.DaoQuery query, int page, int pageSize);
    /**
     * 统计下单任务数量
     *
     * @param query 查询条件
     * @return 任务数量
     */
    long countOrderTasks(OrderTaskMapper.DaoQuery query);
    /**
     * 任务分配方法
     *
     * @param taskId 任务ID
     * @return 任务状态
     * @throws OrderTaskException 任务异常
     */
    int assignTask(Long taskId) throws OrderTaskException;
}
src/main/java/com/taoke/autopay/task/KeyOrderDistributeTask.java
@@ -11,6 +11,7 @@
import com.taoke.autopay.utils.Constant;
import com.taoke.autopay.utils.StringUtil;
import com.taoke.autopay.utils.order.DYOrderApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@@ -23,11 +24,20 @@
@Configuration
@EnableScheduling
public class KeyOrderDistributeTask {
    @Value("${task-enable}")
    private boolean taskEnable;
    @Resource
    private KeyOrderService keyOrderService;
    @Scheduled(cron = "0/5 * * * * ? ")
    private void distribute() {
        if(!taskEnable){
            return;
        }
        try {
            List<KeyOrder> results = keyOrderService.listNotDistributed(1, 20);
            if (results != null) {
@@ -58,6 +68,9 @@
     **/
    @Scheduled(cron = "0/5 * * * * ? ")
    private void repaireProcessedOrders() {
        if(!taskEnable){
            return;
        }
        // TODO 待完成
        KeyOrderMapper.DaoQuery query = new KeyOrderMapper.DaoQuery();
        // 修正1分钟到1小时的数据之前执行的数据
@@ -113,6 +126,9 @@
    @Scheduled(cron = "0/5 * * * * ? ")
    private void processPayFail() {
        if(!taskEnable){
            return;
        }
        for (int i = 0; i < 10; i++) {
            // 一次最多处理10条数据
            orderPayFailProcessor.processFromQueue();
@@ -121,6 +137,9 @@
    @Scheduled(cron = "0 0 3 * * ? ")
    private void clearProcessPayFailCache() {
        if(!taskEnable){
            return;
        }
        orderPayFailProcessor.clearCacheData();
    }
@@ -130,6 +149,9 @@
    // 处理设备下线
    @Scheduled(cron = "0 0/1 * * * ? ")
    private void processPayClientOffLine() {
        if(!taskEnable){
            return;
        }
        KeyOrderMapper.DaoQuery daoQuery = new KeyOrderMapper.DaoQuery();
        // 分配时间在最近5分钟到最近30分钟的,状态为未处理的需要重新分配
        daoQuery.stateList = Arrays.asList(new Integer[]{KeyOrder.STATE_NOT_PAY, KeyOrder.STATE_NOT_PROCESS});
src/main/java/com/taoke/autopay/utils/Constant.java
@@ -39,6 +39,8 @@
    public final static  String DB_DAY_FORMAT="yyyyMMdd";
    public final static String AES_KEY="Xcp!`888888!@(,#";
}
src/main/java/com/taoke/autopay/vo/admin/AdminOrderClientInfoVO.java
New file
@@ -0,0 +1,18 @@
package com.taoke.autopay.vo.admin;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import com.taoke.autopay.entity.ClientInfo;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
@Data
@Builder
public class AdminOrderClientInfoVO {
    @Tolerate
    public AdminOrderClientInfoVO(){
    }
    private ClientInfo client;
    private ClientAdditionalInfo additionalInfo;
}
src/main/resources/application-dev.yml
@@ -53,4 +53,5 @@
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.taoke.autopay.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
task-enable: false
src/main/resources/application-pro.yml
@@ -47,4 +47,5 @@
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.taoke.autopay.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
task-enable: true
src/main/resources/application.yml
@@ -1,3 +1,3 @@
spring:
  profiles:
    active: pro
    active: dev
src/main/resources/mapper/ClientAdditionalInfoMapper.xml
New file
@@ -0,0 +1,91 @@
<?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.taoke.autopay.dao.ClientAdditionalInfoMapper">
    <resultMap id="BaseResultMap" type="com.taoke.autopay.entity.ClientAdditionalInfo">
        <id column="client_id" property="id" jdbcType="BIGINT"/>
        <result column="mobile" property="mobile" jdbcType="VARCHAR"/>
        <result column="alipay_account" property="alipayAccount" jdbcType="VARCHAR"/>
        <result column="alipay_password" property="alipayPassword" jdbcType="VARCHAR"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
    </resultMap>
    <sql id="Base_Column_List">client_id,mobile,alipay_account,alipay_password,create_time,update_time</sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_client_additional_info where client_id = #{id,jdbcType=BIGINT}
    </select>
    <select id="selectByPrimaryKeyForUpdate" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_client_additional_info where client_id = #{id,jdbcType=BIGINT} for update
    </select>
    <sql id="listWhereSQL">
        <if test="query.id!=null">AND client_id = #{query.id}</if>
        <if test="query.mobile!=null">AND mobile = #{query.mobile}</if>
        <if test="query.alipayAccount!=null">AND alipay_account = #{query.alipayAccount}</if>
        <if test="query.alipayPassword!=null">AND alipay_password = #{query.alipayPassword}</if>
        <if test="query.minCreateTime!=null">AND create_time >= #{query.minCreateTime}</if>
        <if test="query.maxCreateTime!=null">AND #{query.maxCreateTime} > create_time</if>
        <if test="query.minUpdateTime!=null">AND update_time >= #{query.minUpdateTime}</if>
        <if test="query.maxUpdateTime!=null">AND #{query.maxUpdateTime} > update_time</if>
    </sql>
    <select id="list" resultMap="BaseResultMap">select
        <include refid="Base_Column_List"/>
        from table_client_additional_info where 1=1
        <include refid="listWhereSQL"/>
        <if test="query.sortList!=null">
            <foreach collection="query.sortList" item="item" open=" order by " separator=",">${item}</foreach>
        </if>
        limit #{query.start},#{query.count}
    </select>
    <select id="listByIds" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_client_additional_info where 1=1
        <if test="ids!=null">
            <foreach collection="ids" item="item" open="and (" separator=" or " close=")">client_id=#{item}
            </foreach>
        </if>
    </select>
    <select id="count" resultType="java.lang.Long">select count(*) from table_client_additional_info where 1=1
        <include refid="listWhereSQL"/>
    </select>
    <delete id="deleteByPrimaryKey"
            parameterType="java.lang.Long">delete from table_client_additional_info where client_id = #{id,jdbcType=BIGINT}</delete>
    <insert id="insert" parameterType="com.taoke.autopay.entity.ClientAdditionalInfo" useGeneratedKeys="true"
            keyProperty="id">insert into table_client_additional_info (client_id,mobile,alipay_account,alipay_password,create_time,update_time) values (#{id,jdbcType=BIGINT},#{mobile,jdbcType=VARCHAR},#{alipayAccount,jdbcType=VARCHAR},#{alipayPassword,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP})</insert>
    <insert id="insertSelective" parameterType="com.taoke.autopay.entity.ClientAdditionalInfo" useGeneratedKeys="true"
            keyProperty="id">insert into table_client_additional_info
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">client_id,</if>
            <if test="mobile != null">mobile,</if>
            <if test="alipayAccount != null">alipay_account,</if>
            <if test="alipayPassword != null">alipay_password,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id,jdbcType=BIGINT},</if>
            <if test="mobile != null">#{mobile,jdbcType=VARCHAR},</if>
            <if test="alipayAccount != null">#{alipayAccount,jdbcType=VARCHAR},</if>
            <if test="alipayPassword != null">#{alipayPassword,jdbcType=VARCHAR},</if>
            <if test="createTime != null">#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="updateTime != null">#{updateTime,jdbcType=TIMESTAMP},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKey"
            parameterType="com.taoke.autopay.entity.ClientAdditionalInfo">update table_client_additional_info set mobile = #{mobile,jdbcType=VARCHAR},alipay_account = #{alipayAccount,jdbcType=VARCHAR},alipay_password = #{alipayPassword,jdbcType=VARCHAR},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = #{updateTime,jdbcType=TIMESTAMP} where client_id = #{id,jdbcType=BIGINT}</update>
    <update id="updateByPrimaryKeySelective" parameterType="com.taoke.autopay.entity.ClientAdditionalInfo">update table_client_additional_info
        <set>
            <if test="mobile != null">mobile=#{mobile,jdbcType=VARCHAR},</if>
            <if test="alipayAccount != null">alipay_account=#{alipayAccount,jdbcType=VARCHAR},</if>
            <if test="alipayPassword != null">alipay_password=#{alipayPassword,jdbcType=VARCHAR},</if>
            <if test="createTime != null">create_time=#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="updateTime != null">update_time=#{updateTime,jdbcType=TIMESTAMP},</if>
        </set>
        where client_id = #{id,jdbcType=BIGINT}
    </update>
</mapper>
src/main/resources/mapper/ClientInfoMapper.xml
@@ -10,8 +10,9 @@
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="active_time" property="activeTime" jdbcType="TIMESTAMP"/>
        <result column="rule" property="rule" jdbcType="INTEGER"/>
        <result column="client_type" property="clientType" jdbcType="INTEGER"/>
    </resultMap>
    <sql id="Base_Column_List">id,`name`,account,pwd,create_time,active_time,rule</sql>
    <sql id="Base_Column_List">id,`name`,account,pwd,create_time,active_time,rule,client_type</sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_user where id = #{id,jdbcType=BIGINT}
@@ -30,6 +31,7 @@
        <if test="query.minActiveTime!=null">AND active_time >= #{query.minActiveTime}</if>
        <if test="query.maxActiveTime!=null">AND #{query.maxActiveTime} > active_time</if>
        <if test="query.rule!=null">AND rule = #{query.rule}</if>
        <if test="query.clientType!=null">AND client_type = #{query.clientType}</if>
    </sql>
    <select id="list" resultMap="BaseResultMap">select
        <include refid="Base_Column_List"/>
@@ -57,7 +59,7 @@
    <delete id="deleteByPrimaryKey"
            parameterType="java.lang.Long">delete from table_user where id = #{id,jdbcType=BIGINT}</delete>
    <insert id="insert" parameterType="com.taoke.autopay.entity.ClientInfo" useGeneratedKeys="true"
            keyProperty="id">insert into table_user (id,`name`,account,pwd,create_time,active_time,rule) values (#{id,jdbcType=BIGINT},#{name,jdbcType=VARCHAR},#{account,jdbcType=VARCHAR},#{pwd,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP},#{activeTime,jdbcType=TIMESTAMP},#{rule,jdbcType=INTEGER})</insert>
            keyProperty="id">insert into table_user (id,`name`,account,pwd,create_time,active_time,rule,client_type) values (#{id,jdbcType=BIGINT},#{name,jdbcType=VARCHAR},#{account,jdbcType=VARCHAR},#{pwd,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP},#{activeTime,jdbcType=TIMESTAMP},#{rule,jdbcType=INTEGER},#{clientType,jdbcType=INTEGER})</insert>
    <insert id="insertSelective" parameterType="com.taoke.autopay.entity.ClientInfo" useGeneratedKeys="true"
            keyProperty="id">insert into table_user
        <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -68,6 +70,7 @@
            <if test="createTime != null">create_time,</if>
            <if test="activeTime != null">active_time,</if>
            <if test="rule != null">rule,</if>
            <if test="clientType != null">client_type,</if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -78,10 +81,11 @@
            <if test="createTime != null">#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="activeTime != null">#{activeTime,jdbcType=TIMESTAMP},</if>
            <if test="rule != null">#{rule,jdbcType=INTEGER},</if>
            <if test="clientType != null">#{clientType,jdbcType=INTEGER},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKey"
            parameterType="com.taoke.autopay.entity.ClientInfo">update table_user set `name` = #{name,jdbcType=VARCHAR},account = #{account,jdbcType=VARCHAR},pwd = #{pwd,jdbcType=VARCHAR},create_time = #{createTime,jdbcType=TIMESTAMP},active_time = #{activeTime,jdbcType=TIMESTAMP},rule = #{rule,jdbcType=INTEGER} where id = #{id,jdbcType=BIGINT}</update>
            parameterType="com.taoke.autopay.entity.ClientInfo">update table_user set `name` = #{name,jdbcType=VARCHAR},account = #{account,jdbcType=VARCHAR},pwd = #{pwd,jdbcType=VARCHAR},create_time = #{createTime,jdbcType=TIMESTAMP},active_time = #{activeTime,jdbcType=TIMESTAMP},rule = #{rule,jdbcType=INTEGER},client_type = #{clientType,jdbcType=INTEGER} where id = #{id,jdbcType=BIGINT}</update>
    <update id="updateByPrimaryKeySelective" parameterType="com.taoke.autopay.entity.ClientInfo">update table_user
        <set>
            <if test="name != null">`name`=#{name,jdbcType=VARCHAR},</if>
@@ -90,6 +94,7 @@
            <if test="createTime != null">create_time=#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="activeTime != null">active_time=#{activeTime,jdbcType=TIMESTAMP},</if>
            <if test="rule != null">rule=#{rule,jdbcType=INTEGER},</if>
            <if test="clientType != null">client_type=#{clientType,jdbcType=INTEGER},</if>
        </set>
        where id = #{id,jdbcType=BIGINT}
    </update>
@@ -103,4 +108,4 @@
    </update>
</mapper>
</mapper>
src/main/resources/mapper/CreditExchangeRecordMapper.xml
@@ -38,6 +38,10 @@
        <if test="query.maxCreateTime!=null">AND #{query.maxCreateTime} > create_time</if>
        <if test="query.minUpdateTime!=null">AND update_time >= #{query.minUpdateTime}</if>
        <if test="query.maxUpdateTime!=null">AND #{query.maxUpdateTime} > update_time</if>
        <if test="query.exchangeStatusList!=null">
            <foreach collection="query.exchangeStatusList" item="item" open=" AND (" separator=" or " close=")"> exchange_status = ${item}</foreach>
        </if>
    </sql>
    <select id="list" resultMap="BaseResultMap">select
        <include refid="Base_Column_List"/> from table_credit_exchange_record where 1=1
src/main/resources/mapper/OrderTaskExecutionDetailMapper.xml
New file
@@ -0,0 +1,150 @@
<?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.taoke.autopay.dao.OrderTaskExecutionDetailMapper">
    <resultMap id="BaseResultMap" type="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail">
        <id column="id" property="id" jdbcType="VARCHAR"/>
        <result column="task_id" property="taskId" jdbcType="BIGINT"/>
        <result column="client_id" property="clientId" jdbcType="BIGINT"/>
        <result column="execution_status" property="executionStatus" jdbcType="INTEGER"/>
        <result column="status_description" property="statusDescription" jdbcType="VARCHAR"/>
        <result column="order_no" property="orderNo" jdbcType="VARCHAR"/>
        <result column="product_name" property="productName" jdbcType="VARCHAR"/>
        <result column="shop_name" property="shopName" jdbcType="VARCHAR"/>
        <result column="coupon_code" property="couponCode" jdbcType="VARCHAR"/>
        <result column="order_time" property="orderTime" jdbcType="TIMESTAMP"/>
        <result column="receive_time" property="receiveTime" jdbcType="TIMESTAMP"/>
        <result column="review_time" property="reviewTime" jdbcType="TIMESTAMP"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
    </resultMap>
    <resultMap id="ClientCountResultMap" type="com.taoke.autopay.dto.js2.ClientCountDTO">
        <result column="clientId" property="clientId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
    </resultMap>
    <sql id="Base_Column_List">id,task_id,client_id,execution_status,status_description,execution_time,order_no,product_name,shop_name,coupon_code,order_time,receive_time,review_time,create_time,update_time</sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String">select
        <include refid="Base_Column_List"/>
        from table_order_task_execution_detail where id = #{id,jdbcType=VARCHAR}
    </select>
    <select id="selectByPrimaryKeyForUpdate" resultMap="BaseResultMap" parameterType="java.lang.String">select
        <include refid="Base_Column_List"/>
        from table_order_task_execution_detail where id = #{id,jdbcType=VARCHAR} for update
    </select>
    <sql id="listWhereSQL">
        <if test="query.id!=null">AND id = #{query.id}</if>
        <if test="query.taskId!=null">AND task_id = #{query.taskId}</if>
        <if test="query.clientId!=null">AND client_id = #{query.clientId}</if>
        <if test="query.executionStatus!=null">AND execution_status = #{query.executionStatus}</if>
        <if test="query.statusDescription!=null">AND status_description = #{query.statusDescription}</if>
        <if test="query.orderNo!=null">AND order_no = #{query.orderNo}</if>
        <if test="query.productName!=null">AND product_name = #{query.productName}</if>
        <if test="query.shopName!=null">AND shop_name = #{query.shopName}</if>
        <if test="query.couponCode!=null">AND coupon_code = #{query.couponCode}</if>
        <if test="query.orderTime!=null">AND order_time = #{query.orderTime}</if>
        <if test="query.receiveTime!=null">AND receive_time = #{query.receiveTime}</if>
        <if test="query.reviewTime!=null">AND review_time = #{query.reviewTime}</if>
        <if test="query.minCreateTime!=null">AND create_time >= #{query.minCreateTime}</if>
        <if test="query.maxCreateTime!=null">AND #{query.maxCreateTime} > create_time</if>
        <if test="query.minUpdateTime!=null">AND update_time >= #{query.minUpdateTime}</if>
        <if test="query.maxUpdateTime!=null">AND #{query.maxUpdateTime} > update_time</if>
    </sql>
    <select id="list" resultMap="BaseResultMap">select
        <include refid="Base_Column_List"/>
        from table_order_task_execution_detail where 1=1
        <include refid="listWhereSQL"/>
        <if test="query.sortList!=null">
            <foreach collection="query.sortList" item="item" open=" order by " separator=",">${item}</foreach>
        </if>
        limit #{query.start},#{query.count}
    </select>
    <select id="listByIds" resultMap="BaseResultMap" parameterType="java.lang.String">select
        <include refid="Base_Column_List"/>
        from table_order_task_execution_detail where 1=1
        <if test="ids!=null">
            <foreach collection="ids" item="item" open="and (" separator=" or " close=")">id=#{item}
            </foreach>
        </if>
    </select>
    <select id="count" resultType="java.lang.Long">select count(*) from table_order_task_execution_detail where 1=1
        <include refid="listWhereSQL"/>
    </select>
    <select id="statisticClientIdsCountByStatus" resultMap="ClientCountResultMap">
        SELECT client_id as clientId,count(*) as count
        FROM table_order_task_execution_detail where
                                                   <foreach collection="statusList" open="" separator=" or " item="status" >
                                                       execution_status=#{status}
                                                   </foreach>
        GROUP BY client_id
    </select>
    <delete id="deleteByPrimaryKey"
            parameterType="java.lang.String">delete from table_order_task_execution_detail where id = #{id,jdbcType=VARCHAR}</delete>
    <insert id="insert" parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail" useGeneratedKeys="true"
            keyProperty="id">insert into table_order_task_execution_detail (id,task_id,client_id,execution_status,status_description,execution_time,order_no,product_name,shop_name,coupon_code,order_time,receive_time,review_time,create_time,update_time) values (#{id,jdbcType=VARCHAR},#{taskId,jdbcType=BIGINT},#{clientId,jdbcType=BIGINT},#{executionStatus,jdbcType=INTEGER},#{statusDescription,jdbcType=VARCHAR},#{executionTime,jdbcType=TIMESTAMP},#{orderNo,jdbcType=VARCHAR},#{productName,jdbcType=VARCHAR},#{shopName,jdbcType=VARCHAR},#{couponCode,jdbcType=VARCHAR},#{orderTime,jdbcType=TIMESTAMP},#{receiveTime,jdbcType=TIMESTAMP},#{reviewTime,jdbcType=TIMESTAMP},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP})</insert>
    <insert id="insertSelective" parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail" useGeneratedKeys="true"
            keyProperty="id">insert into table_order_task_execution_detail
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">id,</if>
            <if test="taskId != null">task_id,</if>
            <if test="clientId != null">client_id,</if>
            <if test="executionStatus != null">execution_status,</if>
            <if test="statusDescription != null">status_description,</if>
                <if test="orderNo != null">order_no,</if>
            <if test="productName != null">product_name,</if>
            <if test="shopName != null">shop_name,</if>
            <if test="couponCode != null">coupon_code,</if>
            <if test="orderTime != null">order_time,</if>
            <if test="receiveTime != null">receive_time,</if>
            <if test="reviewTime != null">review_time,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id,jdbcType=VARCHAR},</if>
            <if test="taskId != null">#{taskId,jdbcType=BIGINT},</if>
            <if test="clientId != null">#{clientId,jdbcType=BIGINT},</if>
            <if test="executionStatus != null">#{executionStatus,jdbcType=INTEGER},</if>
            <if test="statusDescription != null">#{statusDescription,jdbcType=VARCHAR},</if>
                <if test="orderNo != null">#{orderNo,jdbcType=VARCHAR},</if>
            <if test="productName != null">#{productName,jdbcType=VARCHAR},</if>
            <if test="shopName != null">#{shopName,jdbcType=VARCHAR},</if>
            <if test="couponCode != null">#{couponCode,jdbcType=VARCHAR},</if>
            <if test="orderTime != null">#{orderTime,jdbcType=TIMESTAMP},</if>
            <if test="receiveTime != null">#{receiveTime,jdbcType=TIMESTAMP},</if>
            <if test="reviewTime != null">#{reviewTime,jdbcType=TIMESTAMP},</if>
            <if test="createTime != null">#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="updateTime != null">#{updateTime,jdbcType=TIMESTAMP},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKey"
            parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail">update table_order_task_execution_detail set task_id = #{taskId,jdbcType=BIGINT},client_id = #{clientId,jdbcType=BIGINT},execution_status = #{executionStatus,jdbcType=INTEGER},status_description = #{statusDescription,jdbcType=VARCHAR},execution_time = #{executionTime,jdbcType=TIMESTAMP},order_no = #{orderNo,jdbcType=VARCHAR},product_name = #{productName,jdbcType=VARCHAR},shop_name = #{shopName,jdbcType=VARCHAR},coupon_code = #{couponCode,jdbcType=VARCHAR},order_time = #{orderTime,jdbcType=TIMESTAMP},receive_time = #{receiveTime,jdbcType=TIMESTAMP},review_time = #{reviewTime,jdbcType=TIMESTAMP},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=VARCHAR}</update>
    <update id="updateByPrimaryKeySelective" parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail">update table_order_task_execution_detail
        <set>
            <if test="taskId != null">task_id=#{taskId,jdbcType=BIGINT},</if>
            <if test="clientId != null">client_id=#{clientId,jdbcType=BIGINT},</if>
            <if test="executionStatus != null">execution_status=#{executionStatus,jdbcType=INTEGER},</if>
            <if test="statusDescription != null">status_description=#{statusDescription,jdbcType=VARCHAR},</if>
                <if test="orderNo != null">order_no=#{orderNo,jdbcType=VARCHAR},</if>
            <if test="productName != null">product_name=#{productName,jdbcType=VARCHAR},</if>
            <if test="shopName != null">shop_name=#{shopName,jdbcType=VARCHAR},</if>
            <if test="couponCode != null">coupon_code=#{couponCode,jdbcType=VARCHAR},</if>
            <if test="orderTime != null">order_time=#{orderTime,jdbcType=TIMESTAMP},</if>
            <if test="receiveTime != null">receive_time=#{receiveTime,jdbcType=TIMESTAMP},</if>
            <if test="reviewTime != null">review_time=#{reviewTime,jdbcType=TIMESTAMP},</if>
            <if test="createTime != null">create_time=#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="updateTime != null">update_time=#{updateTime,jdbcType=TIMESTAMP},</if>
        </set>
        where id = #{id,jdbcType=VARCHAR}
    </update>
</mapper>
src/main/resources/mapper/OrderTaskMapper.xml
New file
@@ -0,0 +1,152 @@
<?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.taoke.autopay.dao.OrderTaskMapper">
    <resultMap id="BaseResultMap" type="com.taoke.autopay.entity.js2.OrderTask">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="keyword_content" property="keywordContent" jdbcType="VARCHAR"/>
        <result column="keyword_md5" property="keywordMd5" jdbcType="VARCHAR"/>
        <result column="product_source" property="productSource" jdbcType="INTEGER"/>
        <result column="product_name" property="productName" jdbcType="VARCHAR"/>
        <result column="shop_name" property="shopName" jdbcType="VARCHAR"/>
        <result column="required_order_count" property="requiredOrderCount" jdbcType="INTEGER"/>
        <result column="completed_order_count" property="completedOrderCount" jdbcType="INTEGER"/>
        <result column="received_order_count" property="receivedOrderCount" jdbcType="INTEGER"/>
        <result column="reviewed_order_count" property="reviewedOrderCount" jdbcType="INTEGER"/>
        <result column="receive_cycle_hours" property="receiveCycleHours" jdbcType="INTEGER"/>
        <result column="order_start_time" property="orderStartTime" jdbcType="TIMESTAMP"/>
        <result column="order_end_time" property="orderEndTime" jdbcType="TIMESTAMP"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
        <result column="estimated_receive_time" property="estimatedReceiveTime" jdbcType="TIMESTAMP"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
        <result column="status_description" property="statusDescription" jdbcType="VARCHAR"/>
    </resultMap>
    <sql id="Base_Column_List">id,keyword_content,keyword_md5,product_source,product_name,shop_name,required_order_count,completed_order_count,received_order_count,reviewed_order_count,receive_cycle_hours,order_start_time,order_end_time,create_time,update_time,estimated_receive_time,status,status_description</sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_order_task where id = #{id,jdbcType=BIGINT}
    </select>
    <select id="selectByPrimaryKeyForUpdate" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_order_task where id = #{id,jdbcType=BIGINT} for update
    </select>
    <sql id="listWhereSQL">
        <if test="query.id!=null">AND id = #{query.id}</if>
        <if test="query.keywordContent!=null">AND keyword_content = #{query.keywordContent}</if>
        <if test="query.keywordMd5!=null">AND keyword_md5 = #{query.keywordMd5}</if>
        <if test="query.productSource!=null">AND product_source = #{query.productSource}</if>
        <if test="query.productName!=null">AND product_name = #{query.productName}</if>
        <if test="query.shopName!=null">AND shop_name = #{query.shopName}</if>
        <if test="query.requiredOrderCount!=null">AND required_order_count = #{query.requiredOrderCount}</if>
        <if test="query.completedOrderCount!=null">AND completed_order_count = #{query.completedOrderCount}</if>
        <if test="query.receivedOrderCount!=null">AND received_order_count = #{query.receivedOrderCount}</if>
        <if test="query.reviewedOrderCount!=null">AND reviewed_order_count = #{query.reviewedOrderCount}</if>
        <if test="query.receiveCycleHours!=null">AND receive_cycle_hours = #{query.receiveCycleHours}</if>
        <if test="query.orderStartTime!=null">AND order_start_time = #{query.orderStartTime}</if>
        <if test="query.orderEndTime!=null">AND order_end_time = #{query.orderEndTime}</if>
        <if test="query.minCreateTime!=null">AND create_time >= #{query.minCreateTime}</if>
        <if test="query.maxCreateTime!=null">AND #{query.maxCreateTime} > create_time</if>
        <if test="query.minUpdateTime!=null">AND update_time >= #{query.minUpdateTime}</if>
        <if test="query.maxUpdateTime!=null">AND #{query.maxUpdateTime} > update_time</if>
        <if test="query.minEstimatedReceiveTime!=null">AND estimated_receive_time >= #{query.minEstimatedReceiveTime}</if>
        <if test="query.maxEstimatedReceiveTime!=null">AND #{query.maxEstimatedReceiveTime} > estimated_receive_time</if>
        <if test="query.status!=null">AND status = #{query.status}</if>
        <if test="query.statusDescription!=null">AND status_description = #{query.statusDescription}</if>
    </sql>
    <select id="list" resultMap="BaseResultMap">select
        <include refid="Base_Column_List"/>
        from table_order_task where 1=1
        <include refid="listWhereSQL"/>
        <if test="query.sortList!=null">
            <foreach collection="query.sortList" item="item" open=" order by " separator=",">${item}</foreach>
        </if>
        limit #{query.start},#{query.count}
    </select>
    <select id="listByIds" resultMap="BaseResultMap" parameterType="java.lang.Long">select
        <include refid="Base_Column_List"/>
        from table_order_task where 1=1
        <if test="ids!=null">
            <foreach collection="ids" item="item" open="and (" separator=" or " close=")">id=#{item}
            </foreach>
        </if>
    </select>
    <select id="count" resultType="java.lang.Long">select count(*) from table_order_task where 1=1
        <include refid="listWhereSQL"/>
    </select>
    <delete id="deleteByPrimaryKey"
            parameterType="java.lang.Long">delete from table_order_task where id = #{id,jdbcType=BIGINT}</delete>
    <insert id="insert" parameterType="com.taoke.autopay.entity.js2.OrderTask" useGeneratedKeys="true"
            keyProperty="id">insert into table_order_task (id,keyword_content,keyword_md5,product_source,product_name,shop_name,required_order_count,completed_order_count,received_order_count,reviewed_order_count,receive_cycle_hours,order_start_time,order_end_time,create_time,update_time,estimated_receive_time,status,status_description) values (#{id,jdbcType=BIGINT},#{keywordContent,jdbcType=VARCHAR},#{keywordMd5,jdbcType=VARCHAR},#{productSource,jdbcType=INTEGER},#{productName,jdbcType=VARCHAR},#{shopName,jdbcType=VARCHAR},#{requiredOrderCount,jdbcType=INTEGER},#{completedOrderCount,jdbcType=INTEGER},#{receivedOrderCount,jdbcType=INTEGER},#{reviewedOrderCount,jdbcType=INTEGER},#{receiveCycleHours,jdbcType=INTEGER},#{orderStartTime,jdbcType=TIMESTAMP},#{orderEndTime,jdbcType=TIMESTAMP},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP},#{estimatedReceiveTime,jdbcType=TIMESTAMP},#{status,jdbcType=INTEGER},#{statusDescription,jdbcType=VARCHAR})</insert>
    <insert id="insertSelective" parameterType="com.taoke.autopay.entity.js2.OrderTask" useGeneratedKeys="true"
            keyProperty="id">insert into table_order_task
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">id,</if>
            <if test="keywordContent != null">keyword_content,</if>
            <if test="keywordMd5 != null">keyword_md5,</if>
            <if test="productSource != null">product_source,</if>
            <if test="productName != null">product_name,</if>
            <if test="shopName != null">shop_name,</if>
            <if test="requiredOrderCount != null">required_order_count,</if>
            <if test="completedOrderCount != null">completed_order_count,</if>
            <if test="receivedOrderCount != null">received_order_count,</if>
            <if test="reviewedOrderCount != null">reviewed_order_count,</if>
            <if test="receiveCycleHours != null">receive_cycle_hours,</if>
            <if test="orderStartTime != null">order_start_time,</if>
            <if test="orderEndTime != null">order_end_time,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="estimatedReceiveTime != null">estimated_receive_time,</if>
            <if test="status != null">status,</if>
            <if test="statusDescription != null">status_description,</if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id,jdbcType=BIGINT},</if>
            <if test="keywordContent != null">#{keywordContent,jdbcType=VARCHAR},</if>
            <if test="keywordMd5 != null">#{keywordMd5,jdbcType=VARCHAR},</if>
            <if test="productSource != null">#{productSource,jdbcType=INTEGER},</if>
            <if test="productName != null">#{productName,jdbcType=VARCHAR},</if>
            <if test="shopName != null">#{shopName,jdbcType=VARCHAR},</if>
            <if test="requiredOrderCount != null">#{requiredOrderCount,jdbcType=INTEGER},</if>
            <if test="completedOrderCount != null">#{completedOrderCount,jdbcType=INTEGER},</if>
            <if test="receivedOrderCount != null">#{receivedOrderCount,jdbcType=INTEGER},</if>
            <if test="reviewedOrderCount != null">#{reviewedOrderCount,jdbcType=INTEGER},</if>
            <if test="receiveCycleHours != null">#{receiveCycleHours,jdbcType=INTEGER},</if>
            <if test="orderStartTime != null">#{orderStartTime,jdbcType=TIMESTAMP},</if>
            <if test="orderEndTime != null">#{orderEndTime,jdbcType=TIMESTAMP},</if>
            <if test="createTime != null">#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="updateTime != null">#{updateTime,jdbcType=TIMESTAMP},</if>
            <if test="estimatedReceiveTime != null">#{estimatedReceiveTime,jdbcType=TIMESTAMP},</if>
            <if test="status != null">#{status,jdbcType=INTEGER},</if>
            <if test="statusDescription != null">#{statusDescription,jdbcType=VARCHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKey"
            parameterType="com.taoke.autopay.entity.js2.OrderTask">update table_order_task set keyword_content = #{keywordContent,jdbcType=VARCHAR},keyword_md5 = #{keywordMd5,jdbcType=VARCHAR},product_source = #{productSource,jdbcType=INTEGER},product_name = #{productName,jdbcType=VARCHAR},shop_name = #{shopName,jdbcType=VARCHAR},required_order_count = #{requiredOrderCount,jdbcType=INTEGER},completed_order_count = #{completedOrderCount,jdbcType=INTEGER},received_order_count = #{receivedOrderCount,jdbcType=INTEGER},reviewed_order_count = #{reviewedOrderCount,jdbcType=INTEGER},receive_cycle_hours = #{receiveCycleHours,jdbcType=INTEGER},order_start_time = #{orderStartTime,jdbcType=TIMESTAMP},order_end_time = #{orderEndTime,jdbcType=TIMESTAMP},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = #{updateTime,jdbcType=TIMESTAMP},estimated_receive_time = #{estimatedReceiveTime,jdbcType=TIMESTAMP},status = #{status,jdbcType=INTEGER},status_description = #{statusDescription,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT}</update>
    <update id="updateByPrimaryKeySelective" parameterType="com.taoke.autopay.entity.js2.OrderTask">update table_order_task
        <set>
            <if test="keywordContent != null">keyword_content=#{keywordContent,jdbcType=VARCHAR},</if>
            <if test="keywordMd5 != null">keyword_md5=#{keywordMd5,jdbcType=VARCHAR},</if>
            <if test="productSource != null">product_source=#{productSource,jdbcType=INTEGER},</if>
            <if test="productName != null">product_name=#{productName,jdbcType=VARCHAR},</if>
            <if test="shopName != null">shop_name=#{shopName,jdbcType=VARCHAR},</if>
            <if test="requiredOrderCount != null">required_order_count=#{requiredOrderCount,jdbcType=INTEGER},</if>
            <if test="completedOrderCount != null">completed_order_count=#{completedOrderCount,jdbcType=INTEGER},</if>
            <if test="receivedOrderCount != null">received_order_count=#{receivedOrderCount,jdbcType=INTEGER},</if>
            <if test="reviewedOrderCount != null">reviewed_order_count=#{reviewedOrderCount,jdbcType=INTEGER},</if>
            <if test="receiveCycleHours != null">receive_cycle_hours=#{receiveCycleHours,jdbcType=INTEGER},</if>
            <if test="orderStartTime != null">order_start_time=#{orderStartTime,jdbcType=TIMESTAMP},</if>
            <if test="orderEndTime != null">order_end_time=#{orderEndTime,jdbcType=TIMESTAMP},</if>
            <if test="createTime != null">create_time=#{createTime,jdbcType=TIMESTAMP},</if>
            <if test="updateTime != null">update_time=#{updateTime,jdbcType=TIMESTAMP},</if>
            <if test="estimatedReceiveTime != null">estimated_receive_time=#{estimatedReceiveTime,jdbcType=TIMESTAMP},</if>
            <if test="status != null">status=#{status,jdbcType=INTEGER},</if>
            <if test="statusDescription != null">status_description=#{statusDescription,jdbcType=VARCHAR},</if>
        </set>
        where id = #{id,jdbcType=BIGINT}
    </update>
</mapper>
src/main/resources/static/admin/index.html
@@ -74,6 +74,14 @@
                                        <dd><a href="javascript:;" data-url="device-list.html" data-id='6' data-text="设备列表"><span class="l-line"></span>设备列表</a></dd>
                    </dl>
                  </li>
                    <li class="layui-nav-item">
                        <a href="javascript:;"><i class="iconfont"></i>JS2系统</a>
                        <dl class="layui-nav-child">
                            <dd><a href="javascript:;" data-url="order-client-list.html" data-id='7' data-text="下单设备列表"><span class="l-line"></span>下单设备列表</a></dd>
                        </dl>
                    </li>
                </ul>
            </div>
            <!--右侧内容-->
src/main/resources/static/admin/order-client-list.html
New file
@@ -0,0 +1,269 @@
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="renderer" content="webkit">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="viewport"
            content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
        <title>下单设备列表</title>
        <link rel="stylesheet" type="text/css" href="layui/css/layui.css" />
        <link rel="stylesheet" type="text/css" href="css/admin.css" />
        <style>
            .layui-table-cell {
                height: auto;
                white-space: normal;
            }
            #add_client{
                padding: 10px;
            }
        </style>
    </head>
    <body>
        <div class="page-content-wrap">
            <form class="layui-form" action="" lay-filter='search'>
                <div class="layui-form-item">
                    <div class="layui-inline">
                        <input type="text" name="account" id="account" placeholder="按登录账号搜索" autocomplete="off"
                            class="layui-input">
                    </div>
                    <div class="layui-inline">
                        <button class="layui-btn layui-btn-normal" lay-submit lay-filter="search" id="search"><i class="layui-icon layui-icon-search"></i>搜索</button>
                        <a href="javascript:void();" class="layui-btn layui-btn-warm" onclick="showCreateClient()"><i class="layui-icon layui-icon-add-circle"></i> 添加设备</a>
                    </div>
                </div>
            </form>
            <div class="layui-form" id="table-list">
                <table class="layui-table" lay-even lay-skin="nob" id="table" lay-filter="table">
                </table>
            </div>
        </div>
        <div id="add_client" style="display: none;">
            <form class="layui-form" action="" lay-filter='add-client'>
              <div class="layui-form-item">
                <label class="layui-form-label">默认密码</label>
                <div class="layui-input-block">
                  <input type="password" name="pwd" required  lay-verify="required" placeholder="输入默认密码" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label class="layui-form-label">数量</label>
                <div class="layui-input-block">
                  <input type="text" name="count" required  lay-verify="required|num" placeholder="输入创建的数量" autocomplete="off" class="layui-input">
                </div>
              </div>
               <div class="layui-form-item">
                  <div class="layui-input-block">
                    <button class="layui-btn" lay-submit lay-filter="add-client">立即创建</button>
                  </div>
                </div>
              </form>
        </div>
        <script src="layui/layui.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
        <script>
            // 修改设备信息
            function updateClient(id) {
                var layerIndex = layer.open({
                    title: "修改下单设备信息",
                    type: 2,
                    area: ['600px', '500px'],
                    shade: 0.3,
                    shadeClose: false,
                    resize: false,
                    content: 'order-client-update.html?id=' + id,
                    btn: ['确定', '取消'],
                    yes: function(index) {
                        //submit方法为弹框内容中的方法
                        window["layui-layer-iframe" + index].submit(function(res) {
                            let fdata = {
                                id: id
                            };
                            for (let key in res) {
                                if (key.indexOf(".") >= 0) {
                                    let k1 = key.split(".")[0];
                                    let k2 = key.split(".")[1];
                                    if (k1 in fdata) {
                                    } else {
                                        fdata[k1] = {};
                                    }
                                    fdata[k1][k2] = res[key];
                                } else {
                                    fdata[key] = res[key]
                                }
                            }
                            for (let key in fdata) {
                                if (typeof (fdata[key]) == 'object') {
                                    fdata[key] = JSON.stringify(fdata[key]);
                                }
                            }
                            try {
                                var loadIndex = layer.load(1);
                                $.post("/admin/api/clientinfo/js2/update", fdata, function(response) {
                                    layer.close(loadIndex);
                                    if (response.code == 0) {
                                        layer.close(layerIndex);
                                        layer.msg("更改成功");
                                        // 重新加载表格
                                        tableIns.reload();
                                    } else {
                                        layer.msg(response.msg);
                                    }
                                }, 'json').fail(function(jqXHR, textStatus, errorThrown) {
                                    layer.close(loadIndex);
                                    layer.msg("网络请求失败");
                                });
                            } catch (e) {
                                console.log(e);
                            }
                        });
                    },
                    cancel: function() {
                    }
                });
            }
            function showCreateClient(){
                // 创建设备
                var index = layer.open({
                    type:1,
                    title:"设备账号创建",
                    content: $("#add_client"),
                    end:function(e){
                        $("#add_client").css("display","none");
                    }
                });
            }
            layui.use(['form', 'jquery', 'layer', 'table'], function() {
                var table = layui.table;
                var form = layui.form;
                var $ = layui.jquery;
                let table_option = {
                    elem: '#table',
                    url: '/admin/api/clientinfo/js2/list', //数据接口
                    where: {
                        'account': $("#account").val()
                    },
                    parseData: function(res) { //res 即为原始返回的数据
                        let flist = new Array();
                        var list = res.data.list;
                        list.forEach(function(e) {
                            var fdata = {}
                            for (let key in e.client) {
                                // 判断属性是否是对象自身的属性而非继承的属性
                                if (e.client.hasOwnProperty(key)) {
                                    fdata["client." + key] = e.client[key];
                                }
                            }
                            if (e.additionalInfo != null) {
                                for (let key in e.additionalInfo) {
                                    if (e.additionalInfo.hasOwnProperty(key)) {
                                        fdata["additionalInfo." + key] = e.additionalInfo[key];
                                    }
                                }
                            }
                            flist.push(fdata);
                        });
                        return {
                            "code": res.code, //解析接口状态
                            "msg": res.msg, //解析提示文本
                            "count": res.data.count, //解析数据长度
                            "data": flist //解析数据列表
                        }
                    },
                    page: true, //开启分页
                    cols: [
                        [ //表头
                            {
                                field: 'client.id',
                                title: '设备ID',
                                width: 100,
                                fixed: 'left'
                            },{
                            field: 'client.name',
                            title: '设备名称',
                            width: 100,
                            fixed: 'left'
                        },
                            {
                                field: 'client.account',
                                title: '登录账号',
                                width: 150
                            },
                            {
                                field: 'additionalInfo.mobile',
                                title: '手机号',
                                width: 120
                            },
                            {
                                field: 'additionalInfo.alipayAccount',
                                title: '支付宝账号',
                                width: 180
                            },
                            {
                                field: 'client.createTime',
                                title: '创建时间',
                                width: 180
                            }, {
                                field: 'client.activeTime',
                                title: '活跃时间',
                                width: 180
                            },
                            {
                                field: '',
                                title: '操作',
                                width: 120,
                                fixed: 'right',
                                align: 'center',
                                templet: function(d) {
                                    return '<div><a href="javascript:void(0)" onclick="updateClient(' + d["client.id"] + ')" class="layui-table-link">修改</a></div>';
                                }
                            }
                        ]
                    ]
                };
                //第一个实例
                let tableIns = table.render(table_option);
                //监听提交
                form.on('submit(search)', function(data) {
                    tableIns.reload({
                        where: data.field,
                        page: {
                            curr: 1 //重新从第 1 页开始
                        }
                    });
                    return false;
                });
                form.on('submit(add-client)', function(data) {
                    $.post("/admin/api/clientinfo/js2/add", data.field, function(response) {
                        if (response.code == 0) {
                            layer.msg("添加成功");
                            // 关闭弹窗
                            layer.closeAll('page');
                            // 重新加载表格
                            tableIns.reload();
                        } else {
                            layer.msg(response.msg);
                        }
                    }, 'json').fail(function(jqXHR, textStatus, errorThrown) {
                        layer.msg("网络请求失败");
                    });
                    return false;
                });
            });
        </script>
    </body>
</html>
src/main/resources/static/admin/order-client-update.html
New file
@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title>修改下单设备信息</title>
    <link rel="stylesheet" type="text/css" href="layui/css/layui.css" />
    <link rel="stylesheet" type="text/css" href="css/admin.css" />
</head>
<body>
    <div class="page-content-wrap">
        <form class="layui-form" action="" lay-filter='edit'>
            <input type="hidden" name="id" id="clientId">
            <div class="layui-form-item">
                <label class="layui-form-label">手机号:</label>
                <div class="layui-input-block">
                    <input type="text" name="mobile" id="mobile" placeholder="请输入手机号" autocomplete="off"
                        class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">支付宝账号:</label>
                <div class="layui-input-block">
                    <input type="text" name="alipayAccount" id="alipayAccount" placeholder="请输入支付宝账号" autocomplete="off"
                        class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">支付宝密码:</label>
                <div class="layui-input-block">
                    <input type="password" name="alipayPassword" id="alipayPassword" placeholder="请输入支付宝密码" autocomplete="off"
                        class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <div class="layui-input-block">
                    <button class="layui-btn layui-btn-normal" lay-submit lay-filter="update">保存</button>
                    <button type="button" class="layui-btn layui-btn-primary" id="cancelBtn">取消</button>
                </div>
            </div>
        </form>
    </div>
    <script src="layui/layui.js" type="text/javascript" charset="utf-8"></script>
    <script src="js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <script>
        layui.use(['form', 'jquery', 'layer'], function() {
            var form = layui.form;
            var $ = layui.jquery;
            var layer = layui.layer;
            // 获取URL参数
            function getQueryString(name) {
                var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
                var r = window.location.search.substr(1).match(reg);
                if (r != null) return unescape(r[2]); return null;
            }
            // 页面加载时获取设备信息
            $(document).ready(function() {
                var clientId = getQueryString("id");
                if (clientId != null) {
                    $.get("/admin/api/clientinfo/js2/getAdditionalInfo", {id: clientId}, function(res) {
                        if (res.code == 0) {
                            var data = res.data;
                            form.val("edit", data);
                        } else {
                            layer.msg("获取设备信息失败");
                        }
                    }, 'json').fail(function() {
                        layer.msg("网络请求失败");
                    });
                }
            });
            // 监听提交
            form.on('submit(update)', function(data) {
                $.post("/admin/api/clientinfo/js2/update", data.field, function(response) {
                    if (response.code == 0) {
                        layer.msg("保存成功", {time: 1000}, function() {
                            // 父页面会处理关闭和刷新
                        });
                    } else {
                        layer.msg(response.msg);
                    }
                }, 'json').fail(function(jqXHR, textStatus, errorThrown) {
                    layer.msg("网络请求失败");
                });
                return false;
            });
            // 取消按钮事件
            $("#cancelBtn").click(function() {
                var index = parent.layer.getFrameIndex(window.name);
                parent.layer.close(index);
            });
        });
        // 提供给父页面调用的提交函数
        function submit(callback) {
            layui.use(['form', 'jquery'], function() {
                var form = layui.form;
                var $ = layui.jquery;
                // 获取表单数据
                var field = {};
                $(".layui-form input").each(function() {
                    if ($(this).attr("name")) {
                        field[$(this).attr("name")] = $(this).val();
                    }
                });
                // 调用回调函数
                callback(field);
            });
        }
    </script>
</body>
</html>
src/test/java/com/taoke/autopay/dao/ClientAdditionalInfoMapperTest.java
New file
@@ -0,0 +1,109 @@
package com.taoke.autopay.dao;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Date;
/**
 * @author
 * @title: ClientAdditionalInfoMapperTest
 * @description: ClientAdditionalInfoMapper单元测试
 * @date 2024/6/14 18:09
 */
@SpringBootTest
public class ClientAdditionalInfoMapperTest {
    @Resource
    private ClientAdditionalInfoMapper clientAdditionalInfoMapper;
    @Test
    public void testInsert() {
        ClientAdditionalInfo info = ClientAdditionalInfo.builder()
                .id(System.currentTimeMillis()) // 使用当前时间戳作为ID
                .mobile("13800138000")
                .alipayAccount("alipay@example.com")
                .alipayPassword("password123")
                .createTime(new Date())
                .updateTime(new Date())
                .build();
        int result = clientAdditionalInfoMapper.insert(info);
        System.out.println("插入结果: " + result);
    }
    @Test
    public void testSelectByPrimaryKey() {
        // 先插入一条测试数据
        long id = System.currentTimeMillis();
        ClientAdditionalInfo info = ClientAdditionalInfo.builder()
                .id(id)
                .mobile("13900139000")
                .alipayAccount("alipay2@example.com")
                .alipayPassword("password456")
                .createTime(new Date())
                .updateTime(new Date())
                .build();
        clientAdditionalInfoMapper.insert(info);
        // 查询刚刚插入的数据
        ClientAdditionalInfo result = clientAdditionalInfoMapper.selectByPrimaryKey(id);
        System.out.println("查询结果: " + result);
    }
    @Test
    public void testUpdateByPrimaryKey() {
        // 先插入一条测试数据
        long id = System.currentTimeMillis();
        ClientAdditionalInfo info = ClientAdditionalInfo.builder()
                .id(id)
                .mobile("13700137000")
                .alipayAccount("alipay3@example.com")
                .alipayPassword("password789")
                .createTime(new Date())
                .updateTime(new Date())
                .build();
        clientAdditionalInfoMapper.insert(info);
        // 更新数据
        ClientAdditionalInfo updateInfo = ClientAdditionalInfo.builder()
                .id(id)
                .mobile("13600136000")
                .alipayAccount("alipay4@example.com")
                .alipayPassword("newpassword123")
                .createTime(info.getCreateTime())
                .updateTime(new Date())
                .build();
        int result = clientAdditionalInfoMapper.updateByPrimaryKey(updateInfo);
        System.out.println("更新结果: " + result);
        // 查询更新后的数据
        ClientAdditionalInfo resultInfo = clientAdditionalInfoMapper.selectByPrimaryKey(id);
        System.out.println("更新后数据: " + resultInfo);
    }
    @Test
    public void testDeleteByPrimaryKey() {
        // 先插入一条测试数据
        long id = System.currentTimeMillis();
        ClientAdditionalInfo info = ClientAdditionalInfo.builder()
                .id(id)
                .mobile("13500135000")
                .alipayAccount("alipay5@example.com")
                .alipayPassword("password999")
                .createTime(new Date())
                .updateTime(new Date())
                .build();
        clientAdditionalInfoMapper.insert(info);
        // 删除数据
        int result = clientAdditionalInfoMapper.deleteByPrimaryKey(id);
        System.out.println("删除结果: " + result);
    }
}