Commit 12a21868 by HlQ

[add]

1.添加点位设计功能
2.添加请求日志功能
3.微信 openid 绑定使用 Spring Event
4.添加机器人推送功能
5.发货管理添加批量导入功能
1 parent d0b75b48
Showing 50 changed files with 2252 additions and 121 deletions
......@@ -13,22 +13,34 @@
<artifactId>Vion-DevOps</artifactId>
<version>1</version>
<properties>
<pg.version>42.6.0</pg.version>
<sqlserver.version>12.2.0.jre8</sqlserver.version>
<lombok.version>1.18.30</lombok.version>
<hutool.version>5.8.25</hutool.version>
<mapstruct-plus.version>1.3.5</mapstruct-plus.version>
<mp.version>3.5.5</mp.version>
<mp-join.version>1.4.7.2</mp-join.version>
<wx-mp.version>4.6.0</wx-mp.version>
<sa-token.verion>1.37.0</sa-token.verion>
<myexcel.version>4.3.3</myexcel.version>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
<version>${pg.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>12.2.0.jre8</version>
<version>${sqlserver.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
......@@ -37,33 +49,37 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.24</version>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>
<version>1.3.5</version>
<version>${mapstruct-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4</version>
<version>${mp.version}</version>
</dependency>
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId>
<version>1.4.7.2</version>
<version>${mp-join.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.5.0</version>
<version>${wx-mp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......@@ -72,22 +88,17 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
<version>${sa-token.verion}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.37.0</version>
<version>${sa-token.verion}</version>
</dependency>
<dependency>
<groupId>com.github.liaochong</groupId>
<artifactId>myexcel</artifactId>
<version>4.3.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.2.0</version>
<version>${myexcel.version}</version>
</dependency>
</dependencies>
......
package vion.advice;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @author HlQ
* @date 2024/1/17
*/
@Component
@RequiredArgsConstructor
@Aspect
@Slf4j
public class LogAspect {
private final ObjectMapper objectMapper;
@Pointcut("execution(* vion.controller.*.*(..))")
public void logPointcut() {
}
@Before("logPointcut()")
public void doBefore(JoinPoint joinPoint) throws JsonProcessingException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (ObjUtil.isNull(attributes)) {
return;
}
HttpServletRequest request = attributes.getRequest();
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
String requestId = (String) request.getAttribute("requestId");
MDC.put("requestId", requestId);
log.info("Request URL:{}, Method:{}, IP:{}, Arguments:{}",
request.getRequestURI(),
request.getMethod(),
ServletUtil.getClientIP(request),
objectMapper.writeValueAsString(request.getParameterMap())
);
}
@AfterReturning(pointcut = "logPointcut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) throws JsonProcessingException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (ObjUtil.isNull(attributes)) {
return;
}
HttpServletRequest request = attributes.getRequest();
// Calculate response time
long startTime = (Long) request.getAttribute("startTime");
long responseTime = System.currentTimeMillis() - startTime;
String resStr = objectMapper.writeValueAsString(result);
if (resStr.length() > 1000) {
resStr = resStr.substring(0, 1000);
}
log.info("Response time: {} ms. Return value: {}",
responseTime,
resStr);
MDC.remove("requestId");
}
}
......@@ -20,7 +20,7 @@ public class UserNameConverter implements CustomWriteConverter<Long, String> {
@Override
public String convert(Long originalData, CustomWriteContext customWriteContext) {
return Opt.ofNullable(((User) redisTemplate.opsForValue().get("dingtalk:user:" + originalData)))
return Opt.ofNullable(((User) redisTemplate.opsForValue().get("dingtalk:user:id:" + originalData)))
.map(User::getUsername)
.orElse("未知");
}
......
......@@ -9,6 +9,8 @@ import vion.dto.DeliveryRecordDTO;
import vion.service.IDeliveryRecordService;
import vion.vo.DeliveryRecordVO;
import java.util.List;
/**
* 发货记录管理
*/
......@@ -38,6 +40,12 @@ public class DeliveryRecordController {
return deliveryRecordService.save(dto);
}
@PostMapping("/deliveryRecord/batch")
@SaCheckPermission(value = "deliveryRecord:save", orRole = "admin")
public String save(@RequestBody List<DeliveryRecordDTO> dto) {
return deliveryRecordService.saveBatch(dto);
}
@PostMapping("/deliveryRecord/{id}")
@SaCheckPermission(value = "deliveryRecord:edit", orRole = "admin")
public String updateById(@PathVariable Long id, DeliveryRecordDTO dto) {
......
package vion.controller;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
......@@ -35,6 +36,11 @@ public class FileController {
@GetMapping("/files")
public Page<FileInfo> getFiles(FileInfoDTO data) {
return fileService.lambdaQuery(converter.convert(data, new FileInfo()))
.eq(ObjUtil.isNotNull(data.getStoreId()), FileInfo::getStoreId, data.getStoreId())
.eq(ObjUtil.isNotNull(data.getSourceId()), FileInfo::getSourceId, data.getSourceId())
.eq(ObjUtil.isNotNull(data.getContractId()), FileInfo::getContractId, data.getContractId())
.eq(ObjUtil.isNotNull(data.getSourceType()), FileInfo::getSourceType, data.getSourceType())
.in(CollUtil.isNotEmpty(data.getSourceTypeList()), FileInfo::getSourceType, data.getSourceTypeList())
.page(Page.of(data.getPageNum(), data.getPageSize()));
}
......@@ -50,21 +56,36 @@ public class FileController {
}
@PostMapping("/upLoadFile")
public String uploadFile(Long storeId, Long sourceId, FileInfo fileInfo, @RequestParam(name = "file") MultipartFile infile) {
public String uploadFile(FileInfo fileInfo, @RequestParam(name = "file") MultipartFile infile) {
try {
//上传url地址
String filename = infile.getOriginalFilename() + "_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
// String path = fileurl + FileUtil.FILE_SEPARATOR + storeId + FileUtil.FILE_SEPARATOR + sourceId + FileUtil.FILE_SEPARATOR + filename;
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
// todo 路径未确定
String path = fileUrl + FileUtil.FILE_SEPARATOR + filename;
File file = FileUtil.touch(path);
infile.transferTo(file);
String sha256 = SecureUtil.sha256(file).toUpperCase();
String extName = FileUtil.extName(file);
return path;
FileInfo tempFileInfo = new FileInfo();
tempFileInfo.setStoreId(fileInfo.getStoreId());
tempFileInfo.setSourceId(fileInfo.getSourceId());
tempFileInfo.setSourceType(fileInfo.getSourceType());
tempFileInfo.setName(filename);
tempFileInfo.setUrl(path);
tempFileInfo.setType(FileUtil.extName(file));
tempFileInfo.setSha256(SecureUtil.sha256(file).toUpperCase());
tempFileInfo.setUploader(fileInfo.getUploader());
if (fileService.save(tempFileInfo)) {
return path;
} else {
return "文件保存失败";
}
} catch (IOException e) {
log.error("上传文件失败", e);
}
return "success";
return "文件保存成功";
}
@PostMapping("/uploadFile1")
......
package vion.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.liaochong.myexcel.core.DefaultExcelBuilder;
import com.github.liaochong.myexcel.core.watermark.Watermark;
import com.github.liaochong.myexcel.utils.AttachmentExportUtil;
import com.github.liaochong.myexcel.utils.WatermarkUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.bind.annotation.*;
import vion.dto.PointInfoDTO;
import vion.model.RejectInfo;
import vion.service.IPointInfoService;
import vion.third.WechatMod;
import vion.vo.PointInfoVO;
import vion.vo.UserVO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.util.Date;
import java.util.List;
/**
* 点位管理
*
* @author HlQ
* @date 2024/1/8
*/
@RestController
@RequestMapping("/api/point")
@RequiredArgsConstructor
@Slf4j
public class PointDesignController {
private final IPointInfoService pointInfoService;
private final WechatMod wechatMod;
@PostMapping("/frontSubmit")
public Object frontSubmit(PointInfoDTO dto) {
return pointInfoService.frontSubmit(dto);
}
@PostMapping
@SaCheckPermission(value = "point:save", orRole = "admin")
public Object save(PointInfoDTO dto) {
return pointInfoService.save(dto);
}
@PostMapping("/{id}")
@SaCheckPermission(value = "point:edit", orRole = "admin")
public String updById(@PathVariable Long id, PointInfoDTO dto) {
return pointInfoService.upd(id, null, dto);
}
@PostMapping("/upd/{uuid}")
public String updById(@PathVariable String uuid, PointInfoDTO dto) {
return pointInfoService.upd(null, uuid, dto);
}
@GetMapping
@SaCheckPermission(value = "point:list", orRole = "admin")
public Page<PointInfoVO> list(PointInfoDTO dto) {
return pointInfoService.list(dto);
}
@GetMapping("/{id}")
@SaCheckPermission(value = "point:query", orRole = "admin")
public PointInfoVO getPointById(@PathVariable Long id) {
return pointInfoService.getPointByDetail(id, null);
}
@GetMapping("/get/{uuid}")
public PointInfoVO getPointByUuid(@PathVariable String uuid) {
return pointInfoService.getPointByDetail(null, uuid);
}
@DeleteMapping("/{id}")
@SaCheckPermission(value = "point:remove", orRole = "admin")
public String delById(@PathVariable Long id) {
return pointInfoService.delById(id);
}
@PostMapping("/client/reject")
public String clientReject(@RequestBody RejectInfo dto) {
return pointInfoService.reject(dto, "client");
}
@PostMapping("/reject")
@SaCheckPermission(value = "point:reject", orRole = "admin")
public String reject(@RequestBody RejectInfo dto) {
return pointInfoService.reject(dto, null);
}
@GetMapping("/reject/{pointId}")
@SaCheckPermission(value = "point:reject:list", orRole = "admin")
public Page<RejectInfo> rejectInfoList(@PathVariable Long pointId, RejectInfo dto) {
return pointInfoService.rejectInfoList(pointId, dto);
}
@GetMapping("/reject/uuid/{uuid}")
public Page<RejectInfo> rejectInfoPage(@PathVariable String uuid, RejectInfo dto) {
return pointInfoService.rejectInfoList(uuid, dto);
}
@GetMapping("/desgin/push")
@SaCheckPermission(value = "point:design:push", orRole = "admin")
public Object designPush(Long pointId, String pushType) {
return pointInfoService.designPush(pointId, null, pushType);
}
@GetMapping("/export")
@SaCheckPermission(value = "point:export", orRole = "admin")
public void pointExport(PointInfoDTO dto, HttpServletResponse response) {
UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
dto.setPageSize(9999);
Page<PointInfoVO> voPage = pointInfoService.list(dto);
voPage.getRecords().forEach(v -> v.setPointUrl("https://yunwei.vionyun.com:8443/wap/setup-device?uuid=" + v.getUuid()));
try (DefaultExcelBuilder<PointInfoVO> pointInfoVODefaultExcelBuilder = DefaultExcelBuilder.of(PointInfoVO.class)) {
Workbook workbook = pointInfoVODefaultExcelBuilder.build(voPage.getRecords());
// 水印添加指定字体,并在服务器上安装 SimSun 字体,解决中文字体变成方块的问题
Watermark watermark = new Watermark();
watermark.setText(user.getUsername() + "-" + user.getPhone());
watermark.setFont(new Font("SimSun", Font.PLAIN, 16));
WatermarkUtil.addWatermark(workbook, watermark);
AttachmentExportUtil.export(workbook, StrUtil.format("点位设计列表_{}", DateUtil.formatDateTime(new Date())), response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@PostMapping("/install/submit/{uuid}")
public String installSubmit(@PathVariable String uuid, @RequestBody List<String> deviceList) {
return pointInfoService.installSubmit(uuid, deviceList);
}
@GetMapping("/getBindQRCode")
public String getBindQRCode(String uuid) {
return wechatMod.genBindOpenidQRCode(uuid);
}
@GetMapping("/getBindOpenid")
public Object genBindOpenidQRCode(String uuid, String code) {
return pointInfoService.bindOpenid(uuid, code);
}
}
......@@ -82,7 +82,7 @@ public class TaskController {
@GetMapping("/task/export")
@SaCheckPermission(value = "task:export", orRole = "admin")
public void defaultBuild(TaskDTO data, HttpServletResponse response) {
public void taskExport(TaskDTO data, HttpServletResponse response) {
UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
data.setPageSize(9999);
Page<TaskVO> voPage = taskService.getTaskList(data);
......
......@@ -6,15 +6,11 @@ import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import vion.third.WechatMod;
import vion.dto.TaskTempDTO;
import vion.model.TaskTemp;
import vion.service.ITaskTempService;
import vion.vo.TaskTempVO;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 预工单
*/
......@@ -25,7 +21,6 @@ import java.io.IOException;
public class TaskTempController {
private final ITaskTempService taskTempService;
private final WechatMod wechatMod;
private final Converter converter;
@GetMapping("/taskTemps")
......@@ -46,17 +41,6 @@ public class TaskTempController {
return taskTempService.saveOrUpdTaskTemp(data);
}
@GetMapping("/taskTemp/wechatCallback")
public Object wechatCallback(String code, HttpServletResponse res) throws IOException {
Object obj = wechatMod.wechatCallback(code);
if (obj instanceof String) {
res.sendRedirect("https://yunwei.vionyun.com/wap/?openid=" + obj);
} else {
return obj;
}
return "success";
}
@PostMapping("/taskTemp/{id}")
@SaCheckPermission(value = "taskTemp:edit", orRole = "admin")
public String upd(@PathVariable(name = "id") Long id, TaskTempDTO data) {
......
......@@ -4,24 +4,24 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import vion.dto.DingDTO;
import vion.dto.UserDTO;
import vion.model.RUserRole;
import vion.model.Role;
import vion.model.TaskTemp;
import vion.model.User;
import vion.service.IRUserRoleService;
import vion.service.IRoleService;
import vion.service.ITaskTempService;
import vion.service.IUserService;
import vion.third.DingMod;
import vion.third.WechatMod;
......@@ -29,7 +29,9 @@ import vion.vo.RoleVO;
import vion.vo.UserVO;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
......@@ -40,7 +42,6 @@ import java.util.stream.Collectors;
public class UserController {
private final IUserService userService;
private final ITaskTempService taskTempService;
private final IRUserRoleService userRoleService;
private final IRoleService roleService;
private final Converter converter;
......@@ -106,14 +107,36 @@ public class UserController {
return "注销成功";
}
@GetMapping("/wechat/callback")
public Object wechatCallback(String code, Long taskTempId) {
Object obj = wechatMod.wechatCallback(code);
if (obj instanceof String) {
return taskTempService.lambdaUpdate().set(TaskTemp::getOpenid, obj).eq(TaskTemp::getId, taskTempId).update(new TaskTemp()) ? new ModelAndView("weChatNeedAttention") : new ModelAndView("weChatError");
/**
* 用于微信公众号自定义菜单里填写的回调地址
*
* @param code 授权码
* @param active 前端标识,根据此标识跳转到不同的页面
* @param res
* @return java.lang.Object
*/
@GetMapping("/wechatCallback")
public Object wechatCallback(String code, Integer active, HttpServletResponse res) throws IOException {
Object obj = wechatMod.getOpenid(code);
if (obj instanceof Map) {
Map<String, String> map = (Map<String, String>) obj;
res.sendRedirect(StrUtil.format("https://yunwei.vionyun.com/wap?openid={}&nickname={}&active={}", map.get("openid"), URLUtil.encode(map.get("nickname")), active));
} else {
return obj;
}
return "success";
}
@GetMapping("/getQRCode")
public String getCallbackQRCode(Long id, Integer flag) {
Assert.notNull(id, "id 不能为空");
Assert.notNull(flag, "flag 不能为空");
return wechatMod.genCallbackQRCode(id, flag);
}
@GetMapping("/getFollowingOpenid")
public Object getOpenid(Long id, Integer flag, String code) {
return wechatMod.getFollowingOpenid(id, flag, code);
}
}
......@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
import vion.model.Contract;
import vion.model.ContractPayment;
import vion.model.Dictionary;
import vion.model.Payment;
import vion.service.*;
import java.math.BigDecimal;
......@@ -35,6 +36,7 @@ public class ContractRunner {
private final IDictionaryService dictionaryService;
private final IContractPaymentService contractPaymentService;
private final IStoreService storeService;
private final IPaymentService paymentService;
private final RedisTemplate redisTemplate;
@Scheduled(cron = "0 0 * * * *")
......@@ -122,8 +124,8 @@ public class ContractRunner {
});
contractService.saveOrUpdateBatch(insOrUpdContractList, 500);
List<ContractPayment> paymentList = contractPaymentService.list();
Map<Long, Map<Integer, Long>> contractId2PaymentMap = paymentList.stream().collect(Collectors.groupingBy(ContractPayment::getContractId, Collectors.toMap(ContractPayment::getPaymentType, ContractPayment::getId)));
List<ContractPayment> existContractPaymentList = contractPaymentService.list();
Map<Long, Map<Integer, Long>> contractId2PaymentMap = existContractPaymentList.stream().collect(Collectors.groupingBy(ContractPayment::getContractId, Collectors.toMap(ContractPayment::getPaymentType, ContractPayment::getId)));
// 合同付款比例
List<ContractPayment> contractPaymentList = new ArrayList<>();
......@@ -203,12 +205,15 @@ public class ContractRunner {
}
contractPaymentService.saveOrUpdateBatch(contractPaymentList);
List<Payment> paymentList = paymentService.list();
Map<String, BigDecimal> no2PaymentMap = paymentList.stream().collect(Collectors.groupingBy(Payment::getContractNo, Collectors.reducing(BigDecimal.ZERO, Payment::getPaymentAmount, BigDecimal::add)));
insOrUpdContractList.forEach(c -> {
String contractNo = c.getContractNo();
Contract exist = contractService.lambdaQuery().eq(Contract::getContractNo, contractNo).one();
Contract updDto = new Contract();
updDto.setId(exist.getId());
updDto.setPaidAmount(exist.getPaidAmount());
updDto.setPaidAmount(no2PaymentMap.getOrDefault(contractNo, BigDecimal.ZERO));
contractPaymentService.calMoney(exist, updDto);
contractService.updateById(updDto);
});
......
......@@ -18,13 +18,27 @@ public class DeliveryRecordDTO extends BaseDTO {
*/
private String contractNo;
/**
* 合同id
*/
private Long contractId;
/**
* 收货人
*/
private String consignee;
/**
* 收货地址
*/
private String address;
/**
* 收货电话
*/
private String phone;
/**
* 合同名称
*/
private String contractName;
......@@ -48,6 +62,21 @@ public class DeliveryRecordDTO extends BaseDTO {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date signDate;
/**
* 快递公司
*/
private String courierCompany;
/**
* 快递单号
*/
private String trackingNumber;
/**
* 发货清单
*/
private String shippingRemark;
private String remark;
private MultipartFile[] files;
......
......@@ -3,6 +3,8 @@ package vion.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class FileInfoDTO extends BaseDTO {
......@@ -13,6 +15,7 @@ public class FileInfoDTO extends BaseDTO {
private String type;
/** 文件来源(1项目、2工单预处理,3工单操作,4巡检) */
private Integer sourceType;
private List<Integer> sourceTypeList;
/** 文件来源id */
private Long sourceId;
/** 合同id */
......
package vion.dto;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;
/**
* @author HlQ
* @date 2024/1/8
*/
@Getter
@Setter
public class PointInfoDTO extends BaseDTO {
private Long id;
/**
* 集团id
*/
private Long accountId;
/**
* 项目名称(用户填写的)
*/
private String projectName;
/**
* 联系人
*/
private String contact;
/**
* 手机号码
*/
private String phone;
/**
* 点位数量
*/
private Integer pointNum;
/**
* 发货状态
*/
private Integer shippingStatus;
/**
* 收货地址
*/
private String shippingAddress;
/**
* 收货联系人
*/
private String receivingContact;
/**
* 收货联系电话
*/
private String receivingPhone;
/**
* 快递公司
*/
private String courierCompany;
/**
* 快递单号
*/
private String trackingNumber;
/**
* 状态
*/
private Integer status;
/**
* 合同编号
*/
private String contractNo;
/**
* 合同id
*/
private Long contractId;
/**
* 是否施工 0:不施工 1:施工
*/
private Integer isConstruct;
/**
* 施工地址
*/
private String constructAddress;
/**
* 施工联系人
*/
private String constructContact;
/**
* 施工联系电话
*/
private String constructPhone;
/**
* 是否开票 0:不开票 1:开票
*/
private Integer isInvoice;
/**
* 发票类型 1:电子发票 2:纸质发票
*/
private Integer invoiceType;
/**
* 发票抬头
*/
private String invoiceHeader;
/**
* 税号
*/
private String taxIdNum;
/**
* 单位地址
*/
private String invoiceAddress;
/**
* 单位电话
*/
private String invoicePhone;
/**
* 开户银行
*/
private String accountBank;
/**
* 银行卡号
*/
private String bankNumber;
/**
* 邮箱地址
*/
private String email;
/**
* 发票邮寄地址
*/
private String invoiceRecAddress;
/**
* 发票联系人
*/
private String invoiceContact;
/**
* 发票联系电话
*/
private String invoiceRecPhone;
/**
* 邮寄发票的快递公司
*/
private String invoiceCourierCompany;
/**
* 邮寄发票的快递单号
*/
private String invoiceTrackingNumber;
/**
* 备注
*/
private String remake;
/**
* uuid
*/
private String uuid;
/**
* 施工方
*/
private String constructionSide;
/**
* 文件来源
*/
private Integer sourceType;
private MultipartFile[] files;
/** 客户上传合同文件 */
private MultipartFile contractFile;
/** 上传的合同范本 */
private MultipartFile contractTemplateFile;
/* 终版合同 */
private MultipartFile finalContractFile;
}
\ No newline at end of file
package vion.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
/**
* @author HlQ
* @date 2024/1/16
*/
@Getter
@Setter
@Builder
public class WxDTO {
/**
* 微信openid
*/
private String openid;
/**
* 微信昵称
*/
private String nickname;
/**
* 单子的id
*/
private Long id;
/**
* flag 1:故障保修 2:点位设计
*/
private Integer flag;
}
package vion.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import vion.dto.WxDTO;
/**
* @author HlQ
* @date 2024/1/16
*/
@Getter
public class WxDTOEvent extends ApplicationEvent {
private final WxDTO wxDTO;
public WxDTOEvent(Object source, WxDTO dto) {
super(source);
this.wxDTO = dto;
}
}
package vion.event.listener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import vion.dto.WxDTO;
import vion.event.WxDTOEvent;
import vion.model.RPointWx;
import vion.model.TaskTemp;
import vion.service.IRPointWxService;
import vion.service.ITaskTempService;
/**
* @author HlQ
* @date 2024/1/16
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class WxDTOEventListener implements ApplicationListener<WxDTOEvent> {
private final ITaskTempService taskTempService;
private final IRPointWxService pointWxService;
@Override
public void onApplicationEvent(WxDTOEvent event) {
WxDTO wxDTO = event.getWxDTO();
Integer flag = wxDTO.getFlag();
String openid = wxDTO.getOpenid();
Long id = wxDTO.getId();
String nickname = wxDTO.getNickname();
if (flag == 1) {
updateTaskTemp(openid, id);
} else if (flag == 2) {
updatePointInfo(openid, nickname, id);
}
}
private void updateTaskTemp(String openid, Long id) {
taskTempService.lambdaUpdate()
.set(TaskTemp::getOpenid, openid)
.eq(TaskTemp::getId, id)
.update(new TaskTemp());
log.info("故障保修单子[id:{}]绑定openid成功!", id);
}
private void updatePointInfo(String openid, String nickname, Long id) {
RPointWx pointWx = new RPointWx();
pointWx.setPointId(id);
pointWx.setOpenid(openid);
pointWx.setWxName(nickname);
pointWxService.save(pointWx);
log.info("点位设计单子[id:{}]绑定openid成功!", id);
}
}
......@@ -18,9 +18,15 @@ public class InterceptorConfig implements WebMvcConfigurer {
StpUtil.renewTimeout(3600L);
}))
.addPathPatterns("/api/**")
.excludePathPatterns("/api/dictionarys")
.excludePathPatterns("/api/upLoadFile", "/api/ding/callback/**", "/api/wechat/**", "/error")
.excludePathPatterns("/api/order/sign/*")
.excludePathPatterns("/api/form/sign/*")
.excludePathPatterns("/api/taskTemp", "/api/taskTemp/wechatCallback");
.excludePathPatterns("/api/wechatCallback")
.excludePathPatterns("/api/taskTemp")
.excludePathPatterns("/api/point/frontSubmit")
.excludePathPatterns("/api/getQRCode", "/api/getFollowingOpenid", "/api/verifyScan")
.excludePathPatterns("/api/point/getBindQRCode", "/api/point/getBindOpenid")
.excludePathPatterns("/api/point/upd/{uuid}", "/api/point/get/{uuid}", "/api/point/install/submit/{uuid}", "/api/point/client/reject", "/api/point/reject/uuid/{uuid}");
}
}
package vion.interceptor;
import cn.hutool.core.util.IdUtil;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class RequestIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
httpServletRequest.setAttribute("requestId", IdUtil.fastSimpleUUID());
chain.doFilter(request, response);
}
}
\ No newline at end of file
package vion.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import vion.model.PointInfo;
/**
* @author HlQ
* @date 2024/1/8
*/
public interface PointInfoMapper extends MPJBaseMapper<PointInfo> {
}
\ No newline at end of file
package vion.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import vion.model.RPointDevice;
/**
* @author HlQ
* @date 2024/1/12
*/
public interface RPointDeviceMapper extends MPJBaseMapper<RPointDevice> {
}
\ No newline at end of file
package vion.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import vion.model.RPointWx;
/**
* @author HlQ
* @date 2024/1/12
*/
public interface RPointWxMapper extends MPJBaseMapper<RPointWx> {
}
\ No newline at end of file
package vion.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import vion.model.RejectInfo;
/**
* @author HlQ
* @date 2024/1/8
*/
public interface RejectInfoMapper extends MPJBaseMapper<RejectInfo> {
}
\ No newline at end of file
......@@ -34,6 +34,18 @@ public class DeliveryRecord {
@TableField(value = "contract_id")
private Long contractId;
/** 收货人 */
@TableField(value = "consignee")
private String consignee;
/** 收货地址 */
@TableField(value = "address")
private String address;
/** 收货电话 */
@TableField(value = "phone")
private String phone;
/**
* 发货日期
*/
......@@ -47,6 +59,24 @@ public class DeliveryRecord {
private Date signDate;
/**
* 快递公司
*/
@TableField(value = "courier_company")
private String courierCompany;
/**
* 快递单号
*/
@TableField(value = "tracking_number")
private String trackingNumber;
/**
* 发货清单
*/
@TableField(value = "shipping_remark")
private String shippingRemark;
/**
* 备注
*/
private String remark;
......
......@@ -26,9 +26,10 @@ public class FileInfo {
private String type;
/**
* 文件来源
* <br>1项目、2工单预处理,3工单操作,4巡检,5合同,6发货记录</br>
* <br>1项目、2工单预处理,3工单操作,4巡检,5合同,6发货记录</br>
* <br>7签订,8到货,9系统初验,10项目终验,11质保,12第一笔维保款,13第二笔维保款,14第三笔维保款</br>
* <br>15结算差异</br>
* <br>16客户提交的门店图纸,17点位设计图,18合同范本,19客户上传的合同,20安装上线,与总部邮件截图</br>
*/
private Integer sourceType;
/** 文件来源id */
......
package vion.model;
import com.baomidou.mybatisplus.annotation.*;
import io.github.linpeilie.annotations.AutoMapper;
import io.github.linpeilie.annotations.AutoMappers;
import lombok.Data;
import vion.dto.PointInfoDTO;
import vion.vo.PointInfoVO;
import java.util.Date;
/**
* @author HlQ
* @date 2024/1/8
*/
@Data
@TableName(value = "tbl_point_info")
@AutoMappers({
@AutoMapper(target = PointInfoVO.class),
@AutoMapper(target = PointInfoDTO.class),
})
public class PointInfo {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 集团id
*/
@TableField(value = "account_id")
private Long accountId;
/**
* 项目名称(用户填写的)
*/
@TableField(value = "project_name")
private String projectName;
/**
* 联系人
*/
@TableField(value = "contact", condition = SqlCondition.LIKE)
private String contact;
/**
* 手机号码
*/
@TableField(value = "phone", condition = SqlCondition.LIKE)
private String phone;
/**
* 点位数量
*/
@TableField(value = "point_num")
private Integer pointNum;
/**
* 发货状态
*/
@TableField(value = "shipping_status")
private Integer shippingStatus;
/**
* 收货地址
*/
@TableField(value = "shipping_address")
private String shippingAddress;
/**
* 收货联系人
*/
@TableField(value = "receiving_contact")
private String receivingContact;
/**
* 收货联系电话
*/
@TableField(value = "receiving_phone")
private String receivingPhone;
/**
* 快递公司
*/
@TableField(value = "courier_company")
private String courierCompany;
/**
* 快递单号
*/
@TableField(value = "tracking_number")
private String trackingNumber;
/**
* 状态
*/
@TableField(value = "\"status\"")
private Integer status;
/**
* 合同编号
*/
@TableField(value = "contract_no")
private String contractNo;
/**
* 是否施工 0:不施工 1:施工
*/
@TableField(value = "is_construct")
private Integer isConstruct;
/**
* 施工地址
*/
@TableField(value = "construct_address")
private String constructAddress;
/**
* 施工联系人
*/
@TableField(value = "construct_contact")
private String constructContact;
/**
* 施工联系电话
*/
@TableField(value = "construct_phone")
private String constructPhone;
/**
* 是否开票 0:不开票 1:开票
*/
@TableField(value = "is_invoice")
private Integer isInvoice;
/**
* 发票类型 1:电子发票 2:纸质发票
*/
@TableField(value = "invoice_type")
private Integer invoiceType;
/**
* 发票抬头
*/
@TableField(value = "invoice_header")
private String invoiceHeader;
/**
* 税号
*/
@TableField(value = "tax_id_num")
private String taxIdNum;
/**
* 单位地址
*/
@TableField(value = "invoice_address")
private String invoiceAddress;
/**
* 单位电话
*/
@TableField(value = "invoice_phone")
private String invoicePhone;
/**
* 开户银行
*/
@TableField(value = "account_bank")
private String accountBank;
/**
* 银行卡号
*/
@TableField(value = "bank_number")
private String bankNumber;
/**
* 邮箱地址
*/
@TableField(value = "email")
private String email;
/**
* 发票邮寄地址
*/
@TableField(value = "invoice_rec_address")
private String invoiceRecAddress;
/**
* 发票联系人
*/
@TableField(value = "invoice_contact")
private String invoiceContact;
/**
* 发票联系电话
*/
@TableField(value = "invoice_rec_phone")
private String invoiceRecPhone;
/**
* 邮寄发票的快递公司
*/
@TableField(value = "invoice_courier_company")
private String invoiceCourierCompany;
/**
* 邮寄发票的快递单号
*/
@TableField(value = "invoice_tracking_number")
private String invoiceTrackingNumber;
/**
* 备注
*/
@TableField(value = "remake")
private String remake;
/**
* 施工方
*/
@TableField(value = "construction_side")
private String constructionSide;
/**
* uuid
*/
@TableField(value = "uuid")
private String uuid;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
\ No newline at end of file
package vion.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* @author HlQ
* @date 2024/1/12
*/
@Data
@TableName(value = "r_point_device")
public class RPointDevice {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 点位记录id
*/
@TableField(value = "point_id")
private Long pointId;
/**
* 设备序列号
*/
@TableField(value = "device_no")
private String deviceNo;
@TableField(value = "create_time")
private Date createTime;
@TableField(value = "update_time")
private Date updateTime;
}
\ No newline at end of file
package vion.model;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author HlQ
* @date 2024/1/12
*/
@Data
@TableName(value = "r_point_wx")
public class RPointWx {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 点位记录id
*/
@TableField(value = "point_id")
private Long pointId;
/**
* 微信昵称
*/
@TableField(value = "wx_name")
private String wxName;
/**
* 微信openid
*/
@TableField(value = "openid")
private String openid;
@TableField(value = "create_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}
\ No newline at end of file
package vion.model;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import vion.dto.BaseDTO;
import java.util.Date;
/**
* @author HlQ
* @date 2024/1/8
*/
@Data
@TableName(value = "tbl_reject_info")
public class RejectInfo extends BaseDTO {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 点位信息id
*/
@TableField(value = "point_id")
private Long pointId;
/**
* 驳回类型
*/
@TableField(value = "\"type\"")
private Integer type;
/**
* 内容
*/
@TableField(value = "content")
private String content;
@TableField(value = "create_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}
\ No newline at end of file
......@@ -6,6 +6,8 @@ import vion.dto.DeliveryRecordDTO;
import vion.model.DeliveryRecord;
import vion.vo.DeliveryRecordVO;
import java.util.List;
/**
* @author HlQ
* @date 2023/12/5
......@@ -16,5 +18,7 @@ public interface IDeliveryRecordService extends MPJBaseService<DeliveryRecord> {
String save(DeliveryRecordDTO dto);
String saveBatch(List<DeliveryRecordDTO> dto);
String updateById(DeliveryRecordDTO dto);
}
package vion.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseService;
import vion.dto.PointInfoDTO;
import vion.model.PointInfo;
import vion.model.RejectInfo;
import vion.vo.PointInfoVO;
import java.util.List;
/**
* @author HlQ
* @date 2024/1/8
*/
public interface IPointInfoService extends MPJBaseService<PointInfo> {
Object frontSubmit(PointInfoDTO dto);
Object save(PointInfoDTO dto);
String upd(Long id, String uuid, PointInfoDTO dto);
Page<PointInfoVO> list(PointInfoDTO dto);
PointInfoVO getPointByDetail(Long id, String uuid);
String delById(Long id);
String reject(RejectInfo dto, String userStr);
Page<RejectInfo> rejectInfoList(Long pointId, RejectInfo dto);
Page<RejectInfo> rejectInfoList(String uuid, RejectInfo dto);
Object designPush(Long pointId, PointInfo pointInfo, String pushType);
String installSubmit(String uuid, List<String> deviceList);
Object bindOpenid(String uuid, String code);
}
package vion.service;
import com.github.yulichang.base.MPJBaseService;
import vion.model.RPointDevice;
/**
* @author HlQ
* @date 2024/1/12
*/
public interface IRPointDeviceService extends MPJBaseService<RPointDevice> {
}
package vion.service;
import com.github.yulichang.base.MPJBaseService;
import vion.model.RPointWx;
/**
* @author HlQ
* @date 2024/1/12
*/
public interface IRPointWxService extends MPJBaseService<RPointWx> {
}
package vion.service;
import com.github.yulichang.base.MPJBaseService;
import vion.model.RejectInfo;
/**
* @author HlQ
* @date 2024/1/8
*/
public interface IRejectInfoService extends MPJBaseService<RejectInfo> {
}
......@@ -4,24 +4,25 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import vion.dto.ContractDTO;
import vion.dto.DeliveryRecordDTO;
import vion.mapper.DeliveryRecordMapper;
import vion.model.Contract;
import vion.model.DeliveryRecord;
import vion.model.FileInfo;
import vion.service.IContractService;
import vion.service.IDeliveryRecordService;
import vion.service.IFileService;
import vion.model.*;
import vion.service.*;
import vion.third.DingMod;
import vion.vo.DeliveryRecordVO;
import vion.vo.UserVO;
......@@ -29,6 +30,8 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author HlQ
......@@ -40,6 +43,10 @@ public class DeliveryRecordServiceImpl extends MPJBaseServiceImpl<DeliveryRecord
private final IFileService fileService;
private final IContractService contractService;
private final IRContractUserService contractUserService;
private final IPointInfoService pointInfoService;
private final DingMod dingMod;
private final RedisTemplate redisTemplate;
private final Converter converter;
@Value("${fileUrl:}")
......@@ -55,16 +62,23 @@ public class DeliveryRecordServiceImpl extends MPJBaseServiceImpl<DeliveryRecord
.like(StrUtil.isNotBlank(dto.getContractName()), Contract::getName, dto.getContractName())
.like(StrUtil.isNotBlank(dto.getCustomerName()), Contract::getCustomerName, dto.getCustomerName())
.orderByDesc(DeliveryRecord::getCreateTime);
return this.selectJoinListPage(Page.of(dto.getPageNum(), dto.getPageSize()), DeliveryRecordVO.class, wrapper);
Page<DeliveryRecordVO> voPage = this.selectJoinListPage(Page.of(dto.getPageNum(), dto.getPageSize()), DeliveryRecordVO.class, wrapper);
Opt.ofEmptyAble(voPage.getRecords())
.map(recs -> recs.stream().map(DeliveryRecordVO::getId).collect(Collectors.toList()))
.map(ids -> {
List<FileInfo> fileInfos = fileService.lambdaQuery().in(FileInfo::getSourceId, ids).eq(FileInfo::getSourceType, 6).list();
return fileInfos.stream().collect(Collectors.groupingBy(FileInfo::getSourceId));
})
.filter(MapUtil::isNotEmpty)
.ifPresent(map -> voPage.getRecords().forEach(rec -> rec.setFileList(map.get(rec.getId()))));
return voPage;
}
@Override
public String save(DeliveryRecordDTO dto) {
DeliveryRecord record = converter.convert(dto, DeliveryRecord.class);
if (this.save(record)) {
ContractDTO contractDTO = new ContractDTO();
contractDTO.setStatus(2);
contractService.updateById(null, dto.getContractNo(), contractDTO);
updStatusAndPushMsg(record);
Opt.ofNullable(dto.getFiles())
.ifPresent(fileList -> {
......@@ -74,7 +88,7 @@ public class DeliveryRecordServiceImpl extends MPJBaseServiceImpl<DeliveryRecord
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);;
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
String path = fileUrl + FileUtil.FILE_SEPARATOR + "delivery" + FileUtil.FILE_SEPARATOR + dto.getContractId() + FileUtil.FILE_SEPARATOR + record.getId() + FileUtil.FILE_SEPARATOR + filename;
File file = FileUtil.touch(path);
try {
......@@ -103,6 +117,66 @@ public class DeliveryRecordServiceImpl extends MPJBaseServiceImpl<DeliveryRecord
}
@Override
public String saveBatch(List<DeliveryRecordDTO> dto) {
List<DeliveryRecord> records = converter.convert(dto, DeliveryRecord.class);
if (this.saveBatch(records)) {
records.forEach(record -> updStatusAndPushMsg(record));
return "新增成功";
} else {
return "新增失败";
}
}
private void updStatusAndPushMsg(DeliveryRecord record) {
ContractDTO contractDTO = new ContractDTO();
contractDTO.setStatus(2);
Contract existContract = contractService.lambdaQuery().eq(Contract::getContractNo, record.getContractNo()).one();
if (existContract.getStatus() < 2) {
contractService.updateById(null, record.getContractNo(), contractDTO);
}
// 更新点位信息的发货状态
pointInfoService.lambdaUpdate().set(PointInfo::getShippingStatus, 1)
.set(PointInfo::getCourierCompany, record.getCourierCompany())
.set(PointInfo::getTrackingNumber, record.getTrackingNumber())
.set(PointInfo::getStatus, 9)
.eq(PointInfo::getContractNo, record.getContractNo()).update(new PointInfo());
Opt.ofNullable(pointInfoService.lambdaQuery().eq(PointInfo::getContractNo, record.getContractNo()).one())
.ifPresent(p -> pointInfoService.designPush(null, p, "发货"));
List<String> useridList = contractUserService.listObjs(Wrappers.<RContractUser>lambdaQuery().select(RContractUser::getUserId).eq(RContractUser::getContractId, existContract.getId()), Object::toString);
Opt.ofNullable(((User) redisTemplate.opsForValue().get("dingtalk:user:name:" + existContract.getSaleName())))
.map(User::getUserid)
.ifPresent(useridList::add);
dingMod.sendMessage(buildMsg(useridList.stream().distinct().collect(Collectors.joining(",")), record, existContract));
}
JSONObject buildMsg(String userid, DeliveryRecord rec, Contract contract) {
JSONObject jsonObj = new JSONObject();
jsonObj.set("agent_id", 2358374016L);
jsonObj.set("userid_list", userid);
JSONObject msg = new JSONObject();
JSONObject content = new JSONObject();
content.set("title", "设备发货提醒");
String markdown = StrUtil.format("#### 发货通知" +
" \n #### 合同名称:{}" +
" \n #### 合同编号:{}" +
" \n #### 快递公司:{}" +
" \n #### 快递单号:{}" +
" \n #### 收件人:{}" +
" \n #### 发货日期:{}" +
" \n #### 发送时间:{}",
contract.getName(), contract.getContractNo(), rec.getCourierCompany(), rec.getTrackingNumber(), rec.getConsignee(), DateUtil.formatDate(rec.getShipDate()), DateUtil.now());
content.set("text", markdown);
msg.set("msgtype", "markdown");
msg.set("markdown", content);
jsonObj.set("msg", msg);
return jsonObj;
}
@Override
public String updateById(DeliveryRecordDTO dto) {
DeliveryRecord record = converter.convert(dto, DeliveryRecord.class);
if (this.updateById(record)) {
......@@ -115,7 +189,7 @@ public class DeliveryRecordServiceImpl extends MPJBaseServiceImpl<DeliveryRecord
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);;
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
String path = fileUrl + FileUtil.FILE_SEPARATOR + "delivery" + FileUtil.FILE_SEPARATOR + deliveryRecord.getContractId() + FileUtil.FILE_SEPARATOR + record.getId() + FileUtil.FILE_SEPARATOR + filename;
File file = FileUtil.touch(path);
try {
......
package vion.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import vion.dto.PointInfoDTO;
import vion.mapper.PointInfoMapper;
import vion.model.*;
import vion.service.*;
import vion.third.DingMod;
import vion.third.WechatMod;
import vion.utils.ResultUtil;
import vion.vo.PointInfoVO;
import vion.vo.UserVO;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author HlQ
* @date 2024/1/8
*/
@Service
@RequiredArgsConstructor
public class PointInfoServiceImpl extends MPJBaseServiceImpl<PointInfoMapper, PointInfo> implements IPointInfoService {
private final IFileService fileService;
private final IRejectInfoService rejectInfoService;
private final IRPointWxService pointWxService;
private final IRPointDeviceService pointDeviceService;
private final WxMpService wxMpService;
private final DingMod dingMod;
private final WechatMod wechatMod;
private final Converter converter;
@Value("${fileUrl:}")
private String fileUrl;
private final static String DING_GROUP_TOKEN = "7570806785271d2b0a766e0172f49620cf04223bf89077f5b676b7d7a4124373";
@Override
public Object frontSubmit(PointInfoDTO dto) {
return insRec(dto);
}
@Override
public Object save(PointInfoDTO dto) {
return insRec(dto);
}
private Object insRec(PointInfoDTO dto) {
PointInfo pointInfo = converter.convert(dto, PointInfo.class);
pointInfo.setUuid(IdUtil.nanoId());
if (this.save(pointInfo)) {
Long id = pointInfo.getId();
Opt.ofNullable(dto.getFiles())
.ifPresent(fileList ->
Arrays.stream(fileList).forEach(infile -> {
dto.setSourceType(16);
saveFile(id, infile, dto);
}));
String content = StrUtil.format("### 新的点位设计需要处理" +
" \n #### 项目名称:{}" +
" \n #### 联系人:{}", dto.getProjectName(), dto.getContact());
dingMod.robotPush(DING_GROUP_TOKEN, buildMsg("点位设计提醒", content));
return MapUtil.<String, Long>builder()
.put("id", pointInfo.getId())
.build();
}
return "提交失败";
}
@Override
public String upd(Long id, String uuid, PointInfoDTO dto) {
PointInfo pointInfo = converter.convert(dto, PointInfo.class);
PointInfo existPoint;
if (ObjUtil.isNull(id) && StrUtil.isNotBlank(uuid)) {
existPoint = this.lambdaQuery().eq(PointInfo::getUuid, uuid).one();
id = existPoint.getId();
} else {
existPoint = this.getById(id);
}
Wrapper<PointInfo> wrapper = Wrappers.<PointInfo>lambdaUpdate()
.eq(ObjUtil.isNotEmpty(id), PointInfo::getId, id)
.eq(StrUtil.isNotBlank(uuid), PointInfo::getUuid, uuid);
if (this.update(pointInfo, wrapper)) {
if (ObjUtil.isNotNull(existPoint.getPointNum()) && existPoint.getPointNum() <= 4 && ObjUtil.isNotNull(pointInfo.getIsConstruct())) {
Integer pointNum = existPoint.getPointNum();
Integer shippingStatus = pointInfo.getIsConstruct();
String templateName = StrUtil.format("订购合同-{}-{}.docx", pointNum, shippingStatus);
String srcPath = fileUrl + FileUtil.FILE_SEPARATOR + "contract" + FileUtil.FILE_SEPARATOR + "template" + FileUtil.FILE_SEPARATOR + templateName;
String tarPath = fileUrl + FileUtil.FILE_SEPARATOR + "point" + FileUtil.FILE_SEPARATOR + id + FileUtil.FILE_SEPARATOR + "template" + FileUtil.FILE_SEPARATOR + templateName;
File tarFile = FileUtil.copy(srcPath, tarPath, true);
FileInfo fileInfo = new FileInfo();
fileInfo.setStoreId(0L);
fileInfo.setSourceId(id);
fileInfo.setSourceType(18);
fileInfo.setName(templateName);
fileInfo.setUrl(tarPath);
fileInfo.setType(FileUtil.extName(tarFile));
fileInfo.setSha256(SecureUtil.sha256(tarFile).toUpperCase());
fileInfo.setUploader("合同模板,系统生成");
fileService.save(fileInfo);
designPush(null, existPoint, "合同范本");
}
final Long finalId = id;
Opt.ofNullable(dto.getContractFile()).ifPresent(infile -> {
String content = StrUtil.format("### 门店合同已上传请及时处理" +
" \n #### 项目名称:{}" +
" \n #### 联系人:{}", existPoint.getProjectName(), existPoint.getContact());
dingMod.robotPush(DING_GROUP_TOKEN, buildMsg("点位设计提醒", content));
dto.setSourceType(19);
saveFile(finalId, infile, dto);
});
Opt.ofNullable(dto.getContractTemplateFile()).ifPresent(infile -> {
Opt.ofNullable(fileService.lambdaQuery().eq(FileInfo::getSourceId, finalId).eq(FileInfo::getSourceType, 18).list()).ifPresent(fileInfos -> {
fileInfos.stream().map(FileInfo::getUrl).forEach(FileUtil::del);
fileService.removeBatchByIds(fileInfos.stream().map(FileInfo::getId).collect(Collectors.toList()));
});
dto.setSourceType(18);
saveFile(finalId, infile, dto);
});
Opt.ofNullable(dto.getFinalContractFile()).ifPresent(infile -> {
dto.setSourceType(5);
saveFile(finalId, infile, dto);
});
Opt.ofNullable(dto.getFiles())
.ifPresent(fileList -> Arrays.stream(fileList).forEach(infile -> {
if (ObjUtil.isNotNull(dto.getSourceType()) && dto.getSourceType().equals(16)) {
String content = StrUtil.format("### 门店新图纸已上传请及时处理" +
" \n #### 项目名称:{}" +
" \n #### 联系人:{}", existPoint.getProjectName(), existPoint.getContact());
dingMod.robotPush(DING_GROUP_TOKEN, buildMsg("点位设计提醒", content));
}
saveFile(finalId, infile, dto);
}));
if (ObjUtil.isNotNull(dto.getIsConstruct()) && dto.getIsConstruct().equals(1)) {
String content = StrUtil.format("### 门店需要施工请及时处理" +
" \n #### 项目名称:{}" +
" \n #### 联系人:{}", existPoint.getProjectName(), existPoint.getContact());
dingMod.robotPush(DING_GROUP_TOKEN, buildMsg("点位设计施工提醒", content));
}
if (ObjUtil.isNotNull(dto.getIsInvoice()) && dto.getIsInvoice().equals(1)) {
String content = StrUtil.format("### 门店需要开票请及时处理" +
" \n #### 项目名称:{}" +
" \n #### 联系人:{}", existPoint.getProjectName(), existPoint.getContact());
dingMod.robotPush(DING_GROUP_TOKEN, buildMsg("点位设计开票提醒", content));
}
return "编辑成功";
}
return "编辑失败";
}
private void saveFile(Long id, MultipartFile infile, PointInfoDTO dto) {
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
String path = getPath(id, dto, filename);
File file = FileUtil.touch(path);
try {
infile.transferTo(file);
} catch (IOException e) {
log.error("保存文件出错", e);
}
FileInfo fileInfo = new FileInfo();
fileInfo.setStoreId(0L);
fileInfo.setSourceId(id);
fileInfo.setContractId(dto.getContractId());
fileInfo.setSourceType(dto.getSourceType());
fileInfo.setName(filename);
fileInfo.setUrl(path);
fileInfo.setType(FileUtil.extName(file));
fileInfo.setSha256(SecureUtil.sha256(file).toUpperCase());
if (StrUtil.isNotBlank(dto.getContact())) {
fileInfo.setUploader(dto.getContact());
} else {
UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
fileInfo.setUploader(user.getUsername());
}
fileInfo.setUploader(dto.getContact());
fileService.save(fileInfo);
}
private String getPath(Long id, PointInfoDTO dto, String filename) {
String path = fileUrl + FileUtil.FILE_SEPARATOR + "point" + FileUtil.FILE_SEPARATOR + id + FileUtil.FILE_SEPARATOR + filename;
if (dto.getSourceType().equals(5)) {
// 合同文件保存到合同相关目录下
// fixme 目前该合同的文件信息的 sourceId 是根据点位设计图来存的。
// fixme 假如合同管理那边查询的话,应该用合同id + sourceType 来查询吗?
path = fileUrl + FileUtil.FILE_SEPARATOR + "contract" + FileUtil.FILE_SEPARATOR + dto.getContractId() + FileUtil.FILE_SEPARATOR + filename;
} else if (dto.getSourceType().equals(18)) {
path = fileUrl + FileUtil.FILE_SEPARATOR + "point" + FileUtil.FILE_SEPARATOR + id + FileUtil.FILE_SEPARATOR + "template" + FileUtil.FILE_SEPARATOR + filename;
}
return path;
}
@Override
public Page<PointInfoVO> list(PointInfoDTO dto) {
MPJLambdaWrapper<PointInfo> wrapper = new MPJLambdaWrapper<>(converter.convert(dto, PointInfo.class))
.selectAll(PointInfo.class)
.selectAs(Account::getName, PointInfoVO::getAccountName)
.leftJoin(Account.class, Account::getId, PointInfo::getAccountId)
.orderByDesc(PointInfo::getCreateTime);
Page<PointInfoVO> pointInfoVO = this.selectJoinListPage(Page.of(dto.getPageNum(), dto.getPageSize()), PointInfoVO.class, wrapper);
Opt.ofEmptyAble(pointInfoVO.getRecords())
.ifPresent(vos -> {
List<Long> ids = vos.stream().map(PointInfoVO::getId).collect(Collectors.toList());
List<FileInfo> fileInfoList = fileService.lambdaQuery()
.in(FileInfo::getSourceId, ids)
.in(FileInfo::getSourceType, ListUtil.of(5, 16, 17, 18, 19, 20)).list();
Map<Long, Long> id2DrawingCntMap = fileInfoList.stream().filter(v -> v.getSourceType().equals(16)).collect(Collectors.groupingBy(FileInfo::getSourceId, Collectors.counting()));
Map<Long, Long> id2DesignCntMap = fileInfoList.stream().filter(v -> v.getSourceType().equals(17)).collect(Collectors.groupingBy(FileInfo::getSourceId, Collectors.counting()));
Map<Long, Long> id2ContractCntMap = fileInfoList.stream().filter(v -> ListUtil.of(5, 18, 19).contains(v.getSourceType())).collect(Collectors.groupingBy(FileInfo::getSourceId, Collectors.counting()));
Map<Long, Long> id2ReceiptCntMap = fileInfoList.stream().filter(v -> v.getSourceType().equals(20)).collect(Collectors.groupingBy(FileInfo::getSourceId, Collectors.counting()));
vos.forEach(vo -> {
vo.setDrawingCnt(id2DrawingCntMap.getOrDefault(vo.getId(), 0L));
vo.setDesignCnt(id2DesignCntMap.getOrDefault(vo.getId(), 0L));
vo.setContractCnt(id2ContractCntMap.getOrDefault(vo.getId(), 0L));
vo.setReceiptCnt(id2ReceiptCntMap.getOrDefault(vo.getId(), 0L));
});
});
return pointInfoVO;
}
@Override
public PointInfoVO getPointByDetail(Long id, String uuid) {
MPJLambdaWrapper<PointInfo> wrapper = new MPJLambdaWrapper<PointInfo>()
.selectAll(PointInfo.class)
.selectCollection(RPointWx.class, PointInfoVO::getWxNameList, map -> map.result(RPointWx::getWxName))
.selectAs(Account::getName, PointInfoVO::getAccountName)
.selectAs(Contract::getTotalAmount, PointInfoVO::getContractAmount)
.leftJoin(Account.class, Account::getId, PointInfo::getAccountId)
.leftJoin(Contract.class, Contract::getContractNo, PointInfo::getContractNo)
.leftJoin(RPointWx.class, RPointWx::getPointId, PointInfo::getId)
.eq(ObjUtil.isNotEmpty(id), PointInfo::getId, id)
.eq(StrUtil.isNotBlank(uuid), PointInfo::getUuid, uuid)
.orderByDesc(PointInfo::getCreateTime);
PointInfoVO pointInfoVO = this.selectJoinOne(PointInfoVO.class, wrapper);
List<FileInfo> fileInfoList = fileService.lambdaQuery().eq(FileInfo::getSourceId, pointInfoVO.getId()).in(FileInfo::getSourceType, ListUtil.of(5, 16, 17, 18, 19, 20)).list();
Map<Integer, List<FileInfo>> type2FileMap = fileInfoList.stream().collect(Collectors.groupingBy(FileInfo::getSourceType));
pointInfoVO.setDrawingFiles(type2FileMap.get(16));
pointInfoVO.setDesignFiles(type2FileMap.get(17));
pointInfoVO.setFinalContractFiles(type2FileMap.get(5));
pointInfoVO.setContractTemplateFiles(type2FileMap.get(18));
pointInfoVO.setContractFiles(type2FileMap.get(19));
pointInfoVO.setReceiptFiles(type2FileMap.get(20));
return pointInfoVO;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String delById(Long id) {
if (this.removeById(id)) {
// todo 文件删除
fileService.lambdaUpdate().eq(FileInfo::getSourceId, id).in(FileInfo::getSourceType, ListUtil.of(5, 16, 17, 18, 19, 20)).remove();
pointWxService.lambdaUpdate().eq(RPointWx::getPointId, id).remove();
rejectInfoService.lambdaUpdate().eq(RejectInfo::getPointId, id).remove();
pointDeviceService.lambdaUpdate().eq(RPointDevice::getPointId, id).remove();
return "删除成功";
}
return "删除失败";
}
@Override
public String reject(RejectInfo dto, String userStr) {
if (dto.getType() == 1) {
this.lambdaUpdate().set(PointInfo::getStatus, 2).eq(PointInfo::getId, dto.getPointId()).update(new PointInfo());
} else if (dto.getType() == 2 && StrUtil.equals(userStr, "client")) {
if (this.lambdaUpdate().set(PointInfo::getStatus, 4).eq(PointInfo::getId, dto.getPointId()).update(new PointInfo())) {
PointInfo info = this.getById(dto.getPointId());
String content = StrUtil.format("### 门店驳回点位实际图,请及时处理" +
" \n #### 项目名称:{}" +
" \n #### 联系人:{}" +
" \n #### 驳回意见:{}", info.getProjectName(), info.getContact(), dto.getContent());
dingMod.robotPush(DING_GROUP_TOKEN, buildMsg("点位设计图驳回提醒", content));
}
} else if (dto.getType() == 3) {
this.lambdaUpdate().set(PointInfo::getStatus, 5).eq(PointInfo::getId, dto.getPointId()).update(new PointInfo());
}
return rejectInfoService.save(dto) ? "提交成功" : "提交失败";
}
@Override
public Page<RejectInfo> rejectInfoList(Long pointId, RejectInfo dto) {
return rejectInfoService.lambdaQuery(dto).orderByDesc(RejectInfo::getCreateTime).page(Page.of(dto.getPageNum(), dto.getPageSize()));
}
@Override
public Page<RejectInfo> rejectInfoList(String uuid, RejectInfo dto) {
MPJLambdaWrapper<RejectInfo> wrapper = new MPJLambdaWrapper<>(dto)
.selectAll(RejectInfo.class)
.leftJoin(PointInfo.class, PointInfo::getId, RejectInfo::getPointId)
.orderByDesc(RejectInfo::getCreateTime);
return rejectInfoService.page(Page.of(dto.getPageNum(), dto.getPageSize()), wrapper);
}
@Override
public Object designPush(Long pointId, PointInfo pointInfo, String pushType) {
if (ObjUtil.isNull(pointInfo)) {
pointInfo = this.getById(pointId);
}
List<RPointWx> pointWxList = pointWxService.lambdaQuery().eq(RPointWx::getPointId, pointInfo.getId()).list();
if (CollUtil.isEmpty(pointWxList)) {
return ResultUtil.error(500, "该点位" + pointInfo.getId() + "没有绑定微信号,无法推送消息。");
}
String content = "";
String url = null;
if (StrUtil.equals(pushType, "图纸")) {
content = "图纸资料不符合要求,请点击查看详情。";
url = "https://yunwei.vionyun.com:8443/wap/re-point";
} else if (StrUtil.equals(pushType, "点位设计")) {
content = "点位设计已完成,请查看详情确认点位信息。";
url = "https://yunwei.vionyun.com:8443/wap/verify-design";
} else if (StrUtil.equals(pushType, "合同范本")) {
content = "您的合同范本已生成,请盖章后再次上传。";
url = "https://yunwei.vionyun.com:8443/wap/verify-contract";
} else if (StrUtil.equals(pushType, "客户合同")) {
content = "合同有误。请点击详情确认信息。";
url = "https://yunwei.vionyun.com:8443/wap/re-contract";
} else if (StrUtil.equals(pushType, "终版合同")) {
content = "合同内容已经审核通过,向您发送合同终版。";
url = "https://yunwei.vionyun.com:8443/wap/final-contract";
} else if (StrUtil.equals(pushType, "发票")) {
content = "发票已寄出,请注意查收。";
url = "https://yunwei.vionyun.com:8443/wap/invoice-express";
} else if (StrUtil.equals(pushType, "发货")) {
content = "您的设备已发货,请点击详情获取快递信息。";
url = "https://yunwei.vionyun.com:8443/wap/express-info";
} else if (StrUtil.equals(pushType, "邮件截图")) {
content = "设备上线证明文件已上传,请点击详情下载。";
url = "https://yunwei.vionyun.com:8443/wap/setup-device";
}
Set<String> resSet = new HashSet<>();
for (RPointWx pointWx : pointWxList) {
String sendRes = wechatPush(pointInfo, pointWx.getOpenid(), content, url);
resSet.add(pointWx.getOpenid() + ":" + sendRes);
}
if (!resSet.contains("失败")) {
return "消息推送成功。";
} else {
String openidStr = resSet.stream().filter(s -> s.contains("失败")).map(errInfo -> {
List<String> split = StrUtil.split(errInfo, ":");
return split.get(0);
}).collect(Collectors.joining(","));
return ResultUtil.error(513, openidStr + "消息推送失败,请手动发送消息或联系管理员。");
}
}
@Override
public String installSubmit(String uuid, List<String> deviceList) {
PointInfo pointInfo = this.lambdaQuery().eq(PointInfo::getUuid, uuid).one();
List<RPointDevice> pointDeviceList = deviceList.stream().map(no -> {
RPointDevice pointDevice = new RPointDevice();
pointDevice.setPointId(pointInfo.getId());
pointDevice.setDeviceNo(no);
return pointDevice;
}).collect(Collectors.toList());
return pointDeviceService.saveBatch(pointDeviceList) ? "提交成功" : "提交失败";
}
@Override
public Object bindOpenid(String uuid, String code) {
try {
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, null);
String openid = userInfo.getOpenid();
WxMpUser wxMpUser = wxMpService.getUserService().userInfo(openid);
if (!wxMpUser.getSubscribe()) {
return new ModelAndView("weChatNeedAttention");
}
String nickname = userInfo.getNickname();
PointInfo pointInfo = this.lambdaQuery().eq(PointInfo::getUuid, uuid).one();
RPointWx pointWx = new RPointWx();
pointWx.setPointId(pointInfo.getId());
pointWx.setOpenid(openid);
pointWx.setWxName(nickname);
pointWxService.save(pointWx);
return new ModelAndView("weChatBindSuccess");
} catch (WxErrorException e) {
log.error("调用微信接口异常!", e);
}
return new ModelAndView("weChatError");
}
private String wechatPush(PointInfo pointInfo, String openid, String content, String url) {
Map<Integer, String> statusMap = MapUtil.<Integer, String>builder()
.put(1, "图纸已上传")
.put(2, "图纸已驳回")
.put(3, "设计待确认")
.put(4, "驳回设计")
.put(5, "合同签订中")
.put(6, "合同待确认")
.put(7, "合同完成")
.put(8, "金蝶订单生成")
.put(9, "已发货")
.put(10, "已签收")
.build();
List<WxMpTemplateData> wxMpTemplateDataList = ListUtil.of(
new WxMpTemplateData("thing4", pointInfo.getProjectName()),
new WxMpTemplateData("phrase11", statusMap.get(pointInfo.getStatus())),
new WxMpTemplateData("time3", DateUtil.formatDateTime(new Date())),
new WxMpTemplateData("thing18", content));
return wechatMod.sendMsg("IzPvAwx4zgXZmm4bwMc8Pka6piBK2sUf4zf9xA4hkNk", openid, wxMpTemplateDataList, StrUtil.format("{}?uuid={}", url, pointInfo.getUuid()));
}
String buildMsg(String title, String content) {
JSONObject jsonObj = new JSONObject();
jsonObj.set("msgtype", "markdown");
JSONObject markdown = new JSONObject();
markdown.set("title", title);
markdown.set("text", content);
jsonObj.set("markdown", markdown);
return jsonObj.toString();
}
}
\ No newline at end of file
package vion.service.impl;
import com.github.yulichang.base.MPJBaseServiceImpl;
import org.springframework.stereotype.Service;
import vion.mapper.RPointDeviceMapper;
import vion.model.RPointDevice;
import vion.service.IRPointDeviceService;
/**
* @author HlQ
* @date 2024/1/12
*/
@Service
public class RPointDeviceServiceImpl extends MPJBaseServiceImpl<RPointDeviceMapper, RPointDevice> implements IRPointDeviceService {
}
package vion.service.impl;
import com.github.yulichang.base.MPJBaseServiceImpl;
import org.springframework.stereotype.Service;
import vion.mapper.RPointWxMapper;
import vion.model.RPointWx;
import vion.service.IRPointWxService;
/**
* @author HlQ
* @date 2024/1/12
*/
@Service
public class RPointWxServiceImpl extends MPJBaseServiceImpl<RPointWxMapper, RPointWx> implements IRPointWxService {
}
package vion.service.impl;
import com.github.yulichang.base.MPJBaseServiceImpl;
import org.springframework.stereotype.Service;
import vion.mapper.RejectInfoMapper;
import vion.model.RejectInfo;
import vion.service.IRejectInfoService;
/**
* @author HlQ
* @date 2024/1/8
*/
@Service
public class RejectInfoServiceImpl extends MPJBaseServiceImpl<RejectInfoMapper, RejectInfo> implements IRejectInfoService {
}
......@@ -132,7 +132,7 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);;
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
String path = fileUrl + FileUtil.FILE_SEPARATOR + statusDTO.getStoreId() + FileUtil.FILE_SEPARATOR + statusDTO.getSourceId() + FileUtil.FILE_SEPARATOR + filename;
File file = FileUtil.touch(path);
try {
......
......@@ -5,12 +5,10 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.*;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
......@@ -134,7 +132,10 @@ public class TaskServiceImpl extends MPJBaseServiceImpl<TaskMapper, Task> implem
}
// 可能直接提工单,而不是预工单确认之后生成的工单
if (data.getTaskTempId() != null) {
if (ObjUtil.isNotNull(data.getTaskTempId())) {
Task existTask = this.lambdaQuery().eq(Task::getTaskTempId, data.getTaskTempId()).one();
Assert.isNull(existTask, "工单已创建,无需再次确认");
taskTempService.lambdaUpdate()
.set(TaskTemp::getStatus, 3)
.set(TaskTemp::getOperator, data.getActiveUser())
......@@ -221,11 +222,12 @@ public class TaskServiceImpl extends MPJBaseServiceImpl<TaskMapper, Task> implem
.put(4, "挂起")
.build();
List<WxMpTemplateData> wxMpTemplateDataList = ListUtil.of(
new WxMpTemplateData("character_string1", existTask.getUuid()),
new WxMpTemplateData("thing7", DesensitizedUtil.chineseName(user.getUsername())),
new WxMpTemplateData("time4", DateUtil.formatDateTime(new Date())),
new WxMpTemplateData("const3", statusMap.get(existTask.getStatus())));
String sentMsg = wechatMod.sendMsg("ueJlVya7uOfYhFlIv28pC0kiHPe1b6Q-gkWsYKkoRWo", openid, wxMpTemplateDataList, null);
new WxMpTemplateData("character_string21", existTask.getUuid()),
new WxMpTemplateData("thing12", DesensitizedUtil.chineseName(user.getUsername())),
new WxMpTemplateData("time11", DateUtil.formatDateTime(new Date())),
new WxMpTemplateData("thing20", existTask.getFaultDescription().length() > 20 ? StrUtil.sub(existTask.getFaultDescription(), 0, 17) + "..." : existTask.getFaultDescription()),
new WxMpTemplateData("phrase25", statusMap.get(existTask.getStatus())));
String sentMsg = wechatMod.sendMsg("H3zWJysLWrcxrUlrgqPnjDV7LyWLaml_Ap9WsLuQDCs", openid, wxMpTemplateDataList, null);
});
}
......@@ -235,7 +237,7 @@ public class TaskServiceImpl extends MPJBaseServiceImpl<TaskMapper, Task> implem
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);;
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
String path = fileUrl + FileUtil.FILE_SEPARATOR + data.getStoreId() + FileUtil.FILE_SEPARATOR + task.getId() + FileUtil.FILE_SEPARATOR + filename;
File file = FileUtil.touch(path);
try {
......
......@@ -3,6 +3,7 @@ package vion.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
......@@ -26,7 +27,6 @@ import vion.service.IFileService;
import vion.service.ITaskTempService;
import vion.service.IUserService;
import vion.third.DingMod;
import vion.third.WechatMod;
import vion.vo.TaskTempVO;
import java.io.File;
......@@ -43,7 +43,6 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
private final IFileService fileService;
private final IUserService userService;
private final DingMod dingMod;
private final WechatMod wechatMod;
private final Converter converter;
@Value("${fileUrl:}")
......@@ -88,7 +87,7 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
String orgName = infile.getOriginalFilename();
String mainName = FileUtil.mainName(orgName);
String fileExt = FileUtil.extName(orgName);
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);;
String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
String path = fileUrl + FileUtil.FILE_SEPARATOR + 0 + FileUtil.FILE_SEPARATOR + taskTemp.getId() + FileUtil.FILE_SEPARATOR + filename;
File file = FileUtil.touch(path);
try {
......@@ -114,11 +113,9 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
String userids = userList.stream().map(User::getUserid).collect(Collectors.joining(","));
dingMod.sendMessage(buildMsg(userids, taskTemp));
if (StrUtil.isNotBlank(data.getOpenid())) {
return "工单提交成功!";
} else {
return wechatMod.genQRCodeUrl(taskTemp.getId());
}
return MapUtil.<String, Long>builder()
.put("id", taskTemp.getId())
.build();
}
JSONObject buildMsg(String userid, TaskTemp taskTemp) {
......
......@@ -148,7 +148,8 @@ public class DingMod {
}
userService.saveOrUpdate(user, Wrappers.<User>lambdaUpdate().eq(User::getUserid, userid));
User one = userService.lambdaQuery().eq(User::getUserid, userid).one();
redisTemplate.opsForValue().set("dingtalk:user:" + one.getId(), user);
redisTemplate.opsForValue().set("dingtalk:user:id:" + one.getId(), user);
redisTemplate.opsForValue().set("dingtalk:user:name:" + one.getUsername(), user);
}
}
});
......@@ -190,7 +191,9 @@ public class DingMod {
*/
public String sendMessage(JSONObject msg) {
String token = getToken();
return HttpUtil.post("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=" + token, msg.toString());
String res = HttpUtil.post("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=" + token, msg.toString());
log.info("钉钉工作通知消息推送:{}", res);
return res;
}
/**
......@@ -329,4 +332,10 @@ public class DingMod {
}
return "";
}
public String robotPush(String access_token, String body) {
String res = HttpUtil.post("https://oapi.dingtalk.com/robot/send?access_token=" + access_token, body);
log.info("钉钉机器人消息推送:{}", res);
return res;
}
}
package vion.third;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -8,10 +10,14 @@ import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import vion.dto.WxDTO;
import vion.event.WxDTOEvent;
import java.util.List;
......@@ -25,18 +31,24 @@ import java.util.List;
public class WechatMod {
private final WxMpService wxMpService;
private final ApplicationEventPublisher publisher;
/**
* 微信扫码获取用户 openid
* 用于微信公众号内提交单子。
* 用户已关注公众号,直接获取 openid 即可。
*
* @param code 授权码
* @return java.lang.Object
*/
public Object wechatCallback(String code) {
public Object getOpenid(String code) {
try {
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
WxOAuth2UserInfo wxMpUser = wxMpService.getOAuth2Service().getUserInfo(accessToken, null);
return wxMpUser.getOpenid();
WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, null);
String openid = userInfo.getOpenid();
String nickname = userInfo.getNickname();
return MapUtil.<String, String>builder()
.put("nickname", nickname)
.put("openid", openid).build();
} catch (WxErrorException e) {
log.error("调用微信接口异常!", e);
}
......@@ -44,14 +56,55 @@ public class WechatMod {
}
/**
* 生成微信网页授权链接
* 生成微信网页授权链接用于 pc 端故障保修、点位设计等页面,绑定openid
*
* @param taskTempId 预工单 id
* @param id 生成单子的 id
* @param flag 1:故障保修 2:点位设计
* @return java.lang.String
*/
public String genQRCodeUrl(Long taskTempId) {
String url = StrUtil.format("https://yunwei.vionyun.com/yunwei/api/wechat/callback?taskTempId={}", taskTempId);
return wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
public String genCallbackQRCode(Long id, Integer flag) {
String redirectUri = StrUtil.format("https://yunwei.vionyun.com/yunwei/api/getFollowingOpenid?id={}&flag={}", id, flag);
return wxMpService.getOAuth2Service().buildAuthorizationUrl(redirectUri, WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
}
/**
* 用户提交单子,需要判断用户是否关注公众号。
* 关注公众号的用户,直接获取 openid,返回前端,前端提交工单(或其他单子)时带上 openid。
* 未关注公众号的用户,直接返回一个页面,提示用户关注公众号。
*
* @param code 授权码
* @return java.lang.Object
*/
public Object getFollowingOpenid(Long id, Integer flag, String code) {
try {
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, null);
String openid = userInfo.getOpenid();
WxMpUser wxMpUser = wxMpService.getUserService().userInfo(openid);
if (!wxMpUser.getSubscribe()) {
return new ModelAndView("weChatNeedAttention");
}
String nickname = userInfo.getNickname();
WxDTO wxDTO = WxDTO.builder().nickname(nickname).openid(openid).id(id).flag(flag).build();
WxDTOEvent wxDTOEvent = new WxDTOEvent(this, wxDTO);
publisher.publishEvent(wxDTOEvent);
return new ModelAndView("weChatSuccess");
} catch (WxErrorException e) {
log.error("调用微信接口异常!", e);
}
return new ModelAndView("weChatError");
}
/**
* 点位设计详情页获取绑定微信二维码
*
* @param uuid 点位记录 uuid
* @return java.lang.String
*/
public String genBindOpenidQRCode(String uuid) {
String redirectUri = StrUtil.format("https://yunwei.vionyun.com/yunwei/api/point/getBindOpenid?uuid={}&rid={}", uuid, IdUtil.fastSimpleUUID());
return wxMpService.getOAuth2Service().buildAuthorizationUrl(redirectUri, WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
}
public String sendMsg(String templateId, String openId, List<WxMpTemplateData> wxMpTemplateDataList, String url) {
......
......@@ -24,6 +24,20 @@ public class DeliveryRecordVO {
*/
private Long contractId;
/**
* 收货人
*/
private String consignee;
/**
* 收货地址
*/
private String address;
/**
* 收货电话
*/
private String phone;
/**
* 客户名称
......@@ -43,6 +57,21 @@ public class DeliveryRecordVO {
private Date signDate;
/**
* 快递公司
*/
private String courierCompany;
/**
* 快递单号
*/
private String trackingNumber;
/**
* 发货清单
*/
private String shippingRemark;
/**
* 备注
*/
private String remark;
......
package vion.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.github.liaochong.myexcel.core.annotation.ExcelColumn;
import com.github.liaochong.myexcel.core.annotation.ExcelModel;
import com.github.liaochong.myexcel.core.constant.LinkType;
import lombok.Getter;
import lombok.Setter;
import vion.model.FileInfo;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* @author HlQ
* @date 2024/1/8
*/
@Getter
@Setter
@ExcelModel(sheetName = "点位设计", includeAllField = false)
public class PointInfoVO {
private Long id;
/**
* 集团id
*/
private Long accountId;
private String accountName;
/**
* 项目名称(用户填写的)
*/
@ExcelColumn(order = 0, title = "门店名称")
private String projectName;
/**
* 联系人
*/
@ExcelColumn(order = 1, title = "联系人")
private String contact;
/**
* 手机号码
*/
@ExcelColumn(order = 2, title = "联系电话")
private String phone;
/**
* 点位数量
*/
@ExcelColumn(order = 3, title = "点位数量")
private Integer pointNum;
/**
* 点位设计图url
*/
@ExcelColumn(order = 4, title = "点位设计图", linkType = LinkType.URL)
private String pointUrl;
/**
* 发货状态
*/
private Integer shippingStatus;
/**
* 收货地址
*/
private String shippingAddress;
/**
* 收货联系人
*/
private String receivingContact;
/**
* 收货联系电话
*/
private String receivingPhone;
/**
* 快递公司
*/
private String courierCompany;
/**
* 快递单号
*/
private String trackingNumber;
/**
* 状态
*/
private Integer status;
/**
* 合同编号
*/
private String contractNo;
/**
* 合同金额
*/
private BigDecimal contractAmount;
/**
* 是否施工 0:不施工 1:施工
*/
private Integer isConstruct;
/**
* 施工地址
*/
private String constructAddress;
/**
* 施工联系人
*/
private String constructContact;
/**
* 施工联系电话
*/
private String constructPhone;
/**
* 是否开票 0:不开票 1:开票
*/
private Integer isInvoice;
/**
* 发票类型 1:电子发票 2:纸质发票
*/
private Integer invoiceType;
/**
* 发票抬头
*/
private String invoiceHeader;
/**
* 税号
*/
private String taxIdNum;
/**
* 单位地址
*/
private String invoiceAddress;
/**
* 单位电话
*/
private String invoicePhone;
/**
* 开户银行
*/
private String accountBank;
/**
* 银行卡号
*/
private String bankNumber;
/**
* 邮箱地址
*/
private String email;
/**
* 发票邮寄地址
*/
private String invoiceRecAddress;
/**
* 发票联系人
*/
private String invoiceContact;
/**
* 发票联系电话
*/
private String invoiceRecPhone;
/**
* 邮寄发票的快递公司
*/
private String invoiceCourierCompany;
/**
* 邮寄发票的快递单号
*/
private String invoiceTrackingNumber;
/**
* 图纸数量
*/
private Long drawingCnt;
/**
* 点位设计图的数量
*/
private Long designCnt;
/**
* 合同相关的数量
*/
private Long contractCnt;
/**
* 回执单的数量
*/
private Long receiptCnt;
/**
* 备注
*/
private String remake;
/**
* 施工方
*/
private String constructionSide;
/**
* uuid
*/
private String uuid;
private List<String> wxNameList;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 客户上传的图纸文件
*/
private List<FileInfo> drawingFiles;
/**
* 点位设计图文件
*/
private List<FileInfo> designFiles;
/**
* 合同范本文件
*/
private List<FileInfo> contractTemplateFiles;
/**
* 终版合同
*/
private List<FileInfo> finalContractFiles;
/**
* 合同文件
*/
private List<FileInfo> contractFiles;
/**
* 回执单文件
*/
private List<FileInfo> receiptFiles;
}
\ No newline at end of file
debug=false
################################## DATABASE ########################################
#spring.datasource.url=jdbc:postgresql://pgm-2ze3cjpyjgjw0bl5uo.pg.rds.aliyuncs.com:1921/work-order
#spring.datasource.username=vion
#spring.datasource.password=jkou72j32m4K5d8k
#spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.dynamic.primary=master
spring.datasource.dynamic.strict=false
spring.datasource.dynamic.datasource.master.url=jdbc:postgresql://pgm-2ze3cjpyjgjw0bl5uo.pg.rds.aliyuncs.com:1921/work-order
spring.datasource.dynamic.datasource.master.username=vion
spring.datasource.dynamic.datasource.master.password=jkou72j32m4K5d8k
spring.datasource.dynamic.datasource.master.driver-class-name=org.postgresql.Driver
spring.datasource.dynamic.datasource.slave_1.url=jdbc:postgresql://47.102.107.41:5432/f_serv
spring.datasource.dynamic.datasource.slave_1.username=postgres
spring.datasource.dynamic.datasource.slave_1.password=zxl123456
spring.datasource.dynamic.datasource.slave_1.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://pgm-2ze3cjpyjgjw0bl5uo.pg.rds.aliyuncs.com:1921/work-order
spring.datasource.username=vion
spring.datasource.password=jkou72j32m4K5d8k
spring.datasource.driver-class-name=org.postgresql.Driver
spring.redis.host=r-2zejlb88mng3q50aw7pd.redis.rds.aliyuncs.com
spring.redis.password=RtOTnx2V
spring.redis.port=6379
spring.redis.database=8
wx.mp.config-storage.type=RedisTemplate
wx.mp.config-storage.key-prefix=wa
wx.mp.config-storage.redis.host=r-2zejlb88mng3q50aw7pd.redis.rds.aliyuncs.com
wx.mp.config-storage.redis.password==RtOTnx2V
wx.mp.config-storage.redis.port=6379
wx.mp.config-storage.redis.database=8
server.port=8011
fileUrl=D:/pic
#fileurl=/nas-data/work-order
......
......@@ -9,7 +9,7 @@
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="log.path" value="logs"/>
<property name="pattern" value="[%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS})] %highlight([%-5level]) [%thread] %green(%logger{50} [%L]) - %msg%n"/>
<property name="pattern" value="[%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS})] %cyan(%X{requestId}) %highlight([%-5level]) [%thread] %green(%logger{50} [%L]) - %msg%n"/>
<property name="datapattern" value="%msg%n"/>
<!--输出到控制台-->
......
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/xml; charset=utf-8" />
</head>
<title>绑定成功</title>
<style>
.center{
line-height: 200px;
height: 100%;
text-align: center;
}
.center p {
line-height: 1.5;
display: inline-block;
vertical-align: middle;
font-size: 6rem;
}
</style>
<body>
<div class="center">
<p>绑定成功</p>
</div>
</body>
</html>
\ No newline at end of file
......@@ -6,8 +6,8 @@
<title>绑定成功</title>
<style>
.center{
line-height: 200px;
height: 100%;
/*line-height: 200px;*/
height: auto;
text-align: center;
}
.center p {
......@@ -23,10 +23,13 @@
</style>
<body >
<div class="center">
<p>用户信息绑定成功,请关注下方公众号(或搜索[VION智慧门店]关注),以接收工单后续进展.</p>
<p>关注工单失败!<br>请先关注【VION智慧门店】公众号,<br>再扫工单二维码以接收工单信息提醒。</p>
</div>
<div class="center">
<img src="data:image/jpeg;base64,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" alt="VION智慧门店"/>
</div>
<div class="center">
<p>长按或扫码关注</p>
</div>
</body>
</html>
\ No newline at end of file
......@@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/xml; charset=utf-8" />
</head>
<title>绑定成功</title>
<title>扫码成功</title>
<style>
.center{
line-height: 200px;
......@@ -19,7 +19,7 @@
</style>
<body>
<div class="center">
<p>绑定成功</p>
<p>扫码成功</p>
</div>
</body>
</html>
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!