简历文档v1.0
本文最后更新于 2024-03-17,文章内容可能已经过时。
简历文档v1.0
为自己的简历制作的文档.供参考和做面试提示词使用.
1.ERP:
ERP 系统是一种集成的应用软件包,可以用于平衡制造、分销和财务功能。
公司那边有开发人员和较为完整的系统,实验室人员在原有基础上面做优化.
配合公司的后端人员,他们给我们任务,我们找人做.
spring mvc 的项目.
1.1 工作流程:
两个系统
国内系统入驻很多个国内平台商家.
首先进行商品采集 把国内平台上的商品信息采集到自己的数据库,然后再经过处理后,上架到韩国的电商平台.
下单. 韩国客户下单后,中国操作人员同步下单,并定期进行手动的订单同步. 中国下单到指定的物流中心,然后发送过去.
1.2 对接支付宝:
应用场景:商户购买图片翻译,文字翻译等服务.
首先获取appid,appkey,appsecret.我是一开始是使用沙盒的参数,测试完成后换成公司正式的. 把这些通过一个配置类存起来.
然后编写接口,主要有两个方法
一个是发起支付的方法,一个是支付完成后,支付宝平台设置回调的方法.
因为都是自己调用的,所以url可以随便写.
传入订单号,返回一个HTML页面 用户扫码支付后,支付宝异步调用系统给出的接口. 编写完成后,在本地测试回调的时候,还涉及到了内网穿透问题.
内网穿透的核心原理在于将外网 IP 地址与内网 IP 地址建立联系
我当时是使用的ngrok.通过反向代理的方式实现的.
正向代理是代理客户端,反向代理是代理服务器.
ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。
然后就是退款,调用支付宝的退款接口,然后等待返回结果.这也是异步的.
package com.samton.ops.common.util.alipay;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author: HuGoldWater
* @description:
*/
@Slf4j
@RestController
@RequestMapping("payment")
public class AlipayController {
@Autowired
private AliPayResource aliPayResource;
/**
* 前往支付宝进行支付
*/
@RequestMapping (value="/goAlipay")
public String goAlipay(String merchantUserId, String merchantOrderId) throws Exception{
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(aliPayResource.getGatewayUrl(),
aliPayResource.getAppId(),
aliPayResource.getMerchantPrivateKey(),
"json",
aliPayResource.getCharset(),
aliPayResource.getAlipayPublicKey(),
aliPayResource.getSignType());
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(aliPayResource.getReturnUrl());
alipayRequest.setNotifyUrl(aliPayResource.getNotifyUrl());
// 商户订单号, 商户网站订单系统中唯一订单号, 必填
String out_trade_no = merchantOrderId;
// 付款金额, 必填 单位元
String total_amount = "0.01"; // 测试用 1分钱
// 订单名称, 必填
String subject = "抒情熊-付款用户[" + merchantUserId + "]";
// 商品描述, 可空, 目前先用订单名称
String body = subject;
// 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。
String timeout_express = "1h";
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"timeout_express\":\""+ timeout_express +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求
String alipayForm = "";
try {
alipayForm = alipayClient.pageExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
log.info("支付宝支付 - 前往支付页面, alipayForm: \n{}", alipayForm);
// return JsonResult.ok(alipayForm);
return alipayForm;
}
/**
* 支付成功后的支付宝异步通知
*/
@RequestMapping(value="/alipay")
public String alipay(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("支付成功后的支付宝异步通知");
//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map<String,String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
//乱码解决,这段代码在出现乱码时使用
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
boolean signVerified = AlipaySignature.rsaCheckV1(params,
aliPayResource.getAlipayPublicKey(),
aliPayResource.getCharset(),
aliPayResource.getSignType()); //调用SDK验证签名
if(signVerified) {//验证成功
// 商户订单号
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
// 支付宝交易号
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
// 交易状态
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
// 付款金额
String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");
if (trade_status.equals("TRADE_SUCCESS")){
// String merchantReturnUrl = paymentOrderService.updateOrderPaid(out_trade_no, CurrencyUtils.getYuan2Fen(total_amount));
// notifyFoodieShop(out_trade_no,merchantReturnUrl);
}
Date date = new Date();
SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss");
Object date1 = dateFormat.format(date);
log.info("************* 支付成功(支付宝异步通知) - 时间: {} *************", date1);
log.info("* 订单号: {}", out_trade_no);
log.info("* 支付宝交易号: {}", trade_no);
log.info("* 实付金额: {}", total_amount);
log.info("* 交易状态: {}", trade_status);
log.info("*****************************************************************************");
return "success";
}else {
Date date = new Date();
SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss");
Object date1 = dateFormat.format(date);
//验证失败
log.info("验签失败, 时间: {}", date1);
return "fail";
}
}
}
package com.samton.ops.common.util.alipay;
import lombok.Data;
import org.springframework.stereotype.Component;
@Data
@Component
public class AliPayResource {
//之前测试的
//private String appId="2021000122690584";
//private String merchantPrivateKey="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0vuB3EqOvJD8E4/AQYCY7Vph5aVbfMKJxc1lceE+es1dk0EQ8VWmYXom1P2zeBDA5MfHl+cIZx7l15J+FkEkc6hP6vMTuDtS0VDxJ36P/Frp2V3St42MTw2aHVvWgbloOGJeNY4R5zSGpN+Louhjwi5GZjWHkDluSNxI7Y2orCPk3NwqSnxnRjTSYzK19A1FYELFyzTIek2ncNRe2snT4AyDym/vl1WxDSYx0MhFhuJD4DWo6viX4tDQYPRz3TTqo20N82McYBzBEEYgJYhbe9/FBve7Vydd3QeiWvd9EkGaiPovJCg+MxCUYn9dZwZ315C3G1/ctwOceKMi5t+krAgMBAAECggEAK6nQ3/MIx86hyrSl0c7obX1F6E6iRdih5XZQKB6IXXZFrn0BfvHDSKPN8JMZ4ahxXd/K6Bul4ER3cRuBzepFP07s9K2VhUzf5ZBT4CS+oWkEMoJ+FWPRE30oz5kaTV9bMfyO4AEih6oeb5qonkAWtkWBLu8Qrt8pD/Ft3hruEub3tKQQMKM+g00PISyq7QuKLgM335EZOSZIKguKIErCSqYtms0qRB67uyfnLnYqnGtOMwv0Crs6ZMtgtT8IMwqgqjpMBWM5ui5C5iFkPVq4yCePPBA5HvAjIUHlCCv96fLbXkmqeVEhgv6HB/ZSqKscpYoFnHVvCg+lDAsljy1zQQKBgQDrQ9S2+1uZl0K84xYepeTIGCk7kA8TXoAFK3WwYQeIfqsRvetQIYT1NX0/OYE9Sx9+8uygRtJRj4XhdiRSw58gqyGWJYsPnycDbPQ5CcvqEVsuDpW2ZrYZm1Nm424gUn7pSyC8XBCVgOh8As5Ni7BqNvy7oXyAN1XXuu8Z5IdhhQKBgQDErPPGxegO7vJV2LUytaPqwro4IwSbi29YhoRZTU9L2lrms2qbMC7VlZDCUy6tPS4tmZi3/kgb0PZZ3f5xK3wb+3pzAWwANqT57nV+GuXjzHFO2SHta29LOaKZ22VhMbM7FyHO3CoR2KGs3G0dKQrHEwIsDu3XeNUl9zVCQ3vG7wKBgQDAQ0C1ARnMnRbHMnXDOiOLemNH7+TCGXpZvziAmesEGzBGYYTKiXoUwk/GuYHqy1fD8VZ7bSU3zijFJj1s/b0vf2sFP00zyQajAAleC6l/cgunyfeDhtDOgGdaMAaxl3lrwh/QjxRmeWCE5+4c5UmYo7NKyx/p0E7w7C22ZVJV2QKBgCVB2kBad1Z034VxswmzLSUo1FwUDihlJqeve9zq702gRL4VWOmjHAwr9CtL1LjOsTPEOBEK46AZWsG1cyD/KtimMBEfQNVdhK0wBiPodopLzV8xdOLkCkZG2c2pqS/bWWelPytPu8x7rEzxyN3QS5FgwXWVMmsyIba6eOVfoVATAoGAZXY7UKsEIYJZ4bKxXC2+aa7P2Igs8mXfhns+LJYH+tcl2amcfjTfwfGF+dnQz6k2ZrZOx6xzbm7V0/VgHb9K28vZIJ4AUSMP2l1PYuNi3WvMb4EjftASv7zbhmjIKRwRZ05KWDXBG/FUn/Q/ZQsVPi/wn2apy6c8wWmA9p9LPDw=";
//private String alipayPublicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiqQtyKGK0T9eu28Qv/qvcpro7zrnv0Uzplc/rTVozMPDHpdpoWITcWivBVJzNFBQMXW7pV8tJqRzQYATMApcDYnMWa6WrK6YfgqZuVoyvMUSNitajTHsxEmVWQTXqnW95QsiZN+22y3TtFQ0F9C4C/KwwaM4oibTsNtlzI2qqzgFtNbsvvq8lsa3FVZO0gC04BMqiemTfc1dCTgQ7kihl4MJNit85t4tmSCPlIOu7Or1BbVRZ3E1RXeXw39EsggCkdsOp+1ljVxOBfpkne99aodfXBNzJrrZboI5BuTY4j5zTFWFZr3mHx0r+htBbKQ4gyquIakKXigZx1Vir6RQWQIDAQAB";
//新生成的
private String appId="2021003196629056";
private String merchantPrivateKey="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+MobPAIduM31gH7RcK7zeLcQrtiOJ5k0lqdPIyxqZCJpw4xkyFknETfpu29VgBsXYVZAj5UpdRAwIN0vBQk89PL83ryb6oKTmXL9955hyFkvXA8KllS2wlvZw+blhQKec1vABRHgAdMqb2g4ZVSpeADecudQ3zimQ+/JmXqJ/dFcz353VKP2MsQO6XelB0V+sYQ/eOVymlgq61rPf7mnbuqB42xXcAKyoHKsdjs++1Xc0jnAPwFNWoK79YYmBfLNJrUGIqP3Mk/J/D7AX75S94nOTSETUgGS4V64rVgh7SLPWHm8UPpp9a5E2Gy6eCoviFASmSnPjQshh/Z5XVwV7AgMBAAECggEBAIRjwzZ/lcV9jb3FE9Q8laDJlo0eoSox5m1bAxH3XjI7rNT7HuSgYwSDithzqNjInhpxpH138wVsgjuN0etZ7rIfgLKP9r/p5h57XMeU16ZCItQtx+VeK4mJ60zEZudtC76+Vh1rvWQD56wIYlv7zhvUZuFu8GtP09sZpKbhJJc1/vPt9j3QKrgLFLTlI/NgkJhKGVBiKZXsYfZXEaJg7REQqRkvvhbIpBMTgDT5U3la3aNuEZ/IFkMVYGoxXnuRMRxJn/Ws4m8g20n8zMEV3id4Ajg0h6huex826YLbqRjH1bZV8JqwxeE4LPGMRJdNxSStLi3w7cdlZqGaD65AW/ECgYEA3Tyt8dNe6mxXBU+8k/2ASs1yOM4RV51bi3tPd13ba0yBY5sumLAX8yyShkHBUpWZYQ9MKW3D2iyI7V08ETQei2Xg8pYQAM50KzEBFFoqLfCdC9wb7JWPfT2thzrgrwIYfLlf+2QxO+rrDijGDJDNVM4BcyitctjtNWLviZ6r8F8CgYEA3BVErC02GWkSFGo8/S5LBVrfKbM3X7BcIoxNq6LvEhPSOe0aAIexIQiRKau+5UpJUyfUq9CQ4hwwUQVuASh0f4+EARq6hhyfbDKo34WA5ZC62d61u+roqydfs54gXUaWQgJtLR8DOzTlgzX0kGTK7vyI5PRgKYtu5QNWqxJM0GUCgYATjIwWRUYq0r3xwzT3orvWYEcKi/LuWgI/1fqUop+D4LPCOHEqnszO+Q5NfLv3by6pa++f7YoT2kGTL9zh7EgSq0LwTKBHYfbT5jWhNcJqYsuNw7pX8nNGbs/JlkNKU5YUV1EK5rSPBdgVXTb630S1jKqGIX8KGe8D+6UM9Q9eYwKBgF8i6n6rRJmTa/dbPWYMnu1/rLxv9l3s9McSc3jghAwCeXwE3JqiDZXECExFK84eYLgLnclv7VFw8gn0GOtzO3jw5xU7IqpasSeqdom5QlD6UWtg9Jp5H37tFFem4UKxAr8iIWPB5jmv0g74QfIxP/AzRlICuZb76UIiQVLOJFppAoGATIx7fd8nEzDclAdbR0X+D1iuxXp6nBHYn1x07kqe/ugCDe+M7awNE+IK6SjC0NYwqq1uUr3KpkNrFChLMzBpb6pZGyNtnBQLpYZvPus+o7qH4itVgbZOazYwfX9bK6JucUNAlv91I4mrtZ4GtVPm7+rSl1j6QugjnlCUGG4V8GM=";
private String alipayPublicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvjKGzwCHbjN9YB+0XCu83i3EK7YjieZNJanTyMsamQiacOMZMhZJxE36btvVYAbF2FWQI+VKXUQMCDdLwUJPPTy/N68m+qCk5ly/feeYchZL1wPCpZUtsJb2cPm5YUCnnNbwAUR4AHTKm9oOGVUqXgA3nLnUN84pkPvyZl6if3RXM9+d1Sj9jLEDul3pQdFfrGEP3jlcppYKutaz3+5p27qgeNsV3ACsqByrHY7PvtV3NI5wD8BTVqCu/WGJgXyzSa1BiKj9zJPyfw+wF++UveJzk0hE1IBkuFeuK1YIe0iz1h5vFD6afWuRNhsungqL4hQEpkpz40LIYf2eV1cFewIDAQAB";
//以前别的平台用的
// private String appId="2017071307739440";
// private String merchantPrivateKey="MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyEIH5VqdTZyUqKm+htChbSEsbsK5wS9vnnZjjuQDOmr8B/GhlVHeAkl+nxB8tM84OOWevD7tukieE4b48mrkmnnNSNC1EOBreIFFFCNeIcDs5JZFV/O1BH/7PNG+lSDkgHyTXoNrJKjlWbSWHuxGYFO2Gv3fSdc0p3hSXhlqPLM0b5IJwBZx6/Qkiy0KYAgbRfhc/0EdXaXlyq4cPXJaJiYTzQB9y8ImCPXGfjA0EjU4kr7f6ZuAvb8FI5Qmpbh/KyW4VB+BAcV2g4AhXofp92UDggq/zq+M/aWvFFgstYHpDebj0O6KE0fnv0iHSOJRc5ZAxpfpCE5R1x8Ff51rFAgMBAAECggEBAJW79+/6BD7IL6JjiPfLjVwlULN6QVXBFKySA+0KtzkFO7Wp0QfUnaEKdVGYRDc4pv+jGiNF8XErifvd8KD54tQszgDES4RgQYekWXLZ2pSq+8I0ayCJzeDDzPvktjWgpBj014BTjWc4EHsy0SpwDn16q7px64qY8OtSCrLYkfJubOxIC0ly2/vwRsbodQW2KPdL5+hBznP719EWMLRecrTezeguwHygOe3OvsJFwWyt/5EBQr7ompFYZKEMqanM2Ccqh6Ttmr+LOm52yhBP5YhNH2zCCiyU7LoVTwG3K0E6G1daa3TYjcmpCbNNfECVadeoS8stuqHw9bcpumKvLAkCgYEA2fq2znTIDqPz+3AWPqjnEbiO2xOri+0GM2aaT/szQ0+VOsHzICN3HZ9PEiz21ceFwUdPguwsvG8pzUm0sQkTlKcO9EPHMFR0ZRFkF++YePWqbP9kD7HOmNVIyS109u4M2NQRqt281GbMS0cp0FZBE9haXxkaJERHgAALfR43B0MCgYEA0R+AckNAIVK3/ZByfuJCpLDIqh/3B13Q/HZEXEG8PX/Bkb5WQb/xIIftbo77SGCbBHKRedncCdvQrdsnfTER0U+McAcYPOGjfoDRayqD+8aMDMZnEa6ms+52Gk4YEUNmHbeC33sVaSDhPRJLXAm2suBqu7jWbickUEDIN2DG4VcCgYEAnbwPDNb07aM2qnwxnKYcj3Y96coSGO8rzYYxpC7iqZKtKhevF1KSn5zoWv6un4QCBhrULqk4tiK63RK47mLjCG7bI2bofNCgaYJsK+X1L5KWAMnOXo0MMwwj33BFc2pPYZgUMNDEE+9PZinY2CmSbgnhW2+Ouy+tjbJ6nc9/goECgYBRTST7x0d8bRNZAjpxN/fe3Vf2RB0fAQtJy5UCJRBQ/IU96zjPsRbGpfHaBL6Owfgif4QtUlSohIwZu1Ub5+LcdEfOGgQDT1nnyZ8hQdM0JFm4cb4KctMeqvvBeFEFDSX7MagwyEJnr7/BpYYkzyX3XyY/uzmqc487oCP226oWWwKBgEJMmIlVP8UuUj3kEPRrY4zX9lLFFPE91fKb9xjy4+qZNzN8ecqQ79is9HcMqQLNHWLhQRedHhCg6pK/dUjm5QcNhg5VifvGg+coGHIYKfqbmdFF92gH834DummL1cdflTZ4M+f/n4xs/HJk1vKl8jwYXvoAyy+JfvUVX2ovtBrZ";
// private String alipayPublicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi7ttZ5erVZrV5i6EzQq5iLlpoYYBCs/7gxMChokDeXPpp+TV2HoZx8Ftlcit0M4GPssTFK4NOg1xuH/NJEROYzxFFrvIup/DG3KmprCM2bB42VwGi43W0r969EgUVO45Pipguk01lqYRCVbjsYA09Zh91DUHEETbVGbQMfYP/3CEjbICIMgUJpqWWi7gtGJmPne0eXIELpp1enkHwvbgMAbL9IMekn0omUTxrDJpbRUBsDFCvSusLv3m4ihYUT4UZtlqXin9wKA1fSoLiMojYGFvGgK8vuUl7d40B0hwEpGlKb9KKsVF7vj7trAnUj4KYg98yAQ8ArynT5imS6uRFwIDAQAB";
private String notifyUrl="https://dbe1-112-6-224-58.ngrok-free.app/ST_ERP_Web_exploded/payment/alipay";
private String returnUrl;
private String signType="RSA2";
private String charset="UTF-8";
//private String gatewayUrl="https://openapi.alipaydev.com/gateway.do";
private String gatewayUrl="https://openapi.alipay.com/gateway.do";
}
1.3 对接阿里机器翻译:
和对接支付宝差不多
都是使用appkey和appsecret的方式去获取支付宝的翻译api的client.
然后调用翻译接口进行翻译.
但是有个大坑.
就是如果不使用maven等方式引入依赖,(会有很多个依赖)会出现版本不匹配问题,看官网的文档也解决不了.mavenrepository上寻找也找不到.
最后联系的阿里的工作人员来解决的.
public static com.aliyun.alimt20181012.Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
// 您的 AccessKey ID
.setAccessKeyId(accessKeyId)
// 您的 AccessKey Secret
.setAccessKeySecret(accessKeySecret);
// 访问的域名
config.endpoint = "mt.cn-hangzhou.aliyuncs.com";
return new com.aliyun.alimt20181012.Client(config);
}
public static Result translate(String text) throws Exception {
try {
// System.out.println("翻译前为:"+text);
com.aliyun.alimt20181012.Client client = createClient("LTAI5tD61xHVx87uUVnWi26B", "hIRefUPwQnKJqf4Wk5bAarhfC8CGYZ");
com.aliyun.alimt20181012.models.TranslateGeneralRequest translateGeneralRequest = new com.aliyun.alimt20181012.models.TranslateGeneralRequest()
.setFormatType("text")
.setSourceLanguage("zh")
.setTargetLanguage("ko")
.setSourceText(text);
// .setScene("general");
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
// 复制代码运行请自行打印 API 的返回值
TranslateGeneralResponse response = client.translateGeneralWithOptions(translateGeneralRequest, runtime);
String text1 = com.aliyun.teautil.Common.toJSONString(response.getBody().data.getTranslated());
String number = com.aliyun.teautil.Common.toJSONString(response.getBody().data.getWordCount());
// System.out.println("翻译后为:"+text1);
// System.out.println(number);
return new Result(text1,number);
} catch (TeaException error) {
error.printStackTrace();
} catch (Exception _error) {
_error.printStackTrace();
} catch (Throwable t){
t.printStackTrace();
}
return null;
}
出了翻译本身,还有业务问题.
业务场景主要出现在商品翻译和客服对话的时候.
对于商品翻译,因为他是要计算用量的,所以翻译之后必须储存下来.
package com.samton.platform.translate.service.impl;//package com.samton.platform.translate.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.samton.erp.api.setMeal.service.TErpUserDataStatisticsService;
import com.samton.listing.api.coupang.entity.LisCoupangProduct;
import com.samton.listing.api.coupang.dao.LisCoupangProductMapper;
import com.samton.platform.framework.base.BaseResult;
import com.samton.platform.translate.bean.CoupangProduct;
import com.samton.platform.translate.bean.LisTranslate;
import com.samton.platform.translate.dao.LisTranslateMapper;
import com.samton.platform.translate.service.TranslateService;
import com.samton.platform.translate.util.Result;
import com.samton.platform.translate.util.TranslateUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
@Service("translateservice")
public class TranslateServiceImpl implements TranslateService {
@Resource
private LisCoupangProductMapper lisCoupangProductMapper;
@Resource
private LisTranslateMapper lisTranslateMapper;
@Resource
private TErpUserDataStatisticsService tErpUserDataStatisticsService;
//
// //1.传入id,查找这一行,得到需要的属性
// //2.解析json,并进行翻译
// //4.把数据返回给前端
// //3.在数据库中替换翻译结果
public LisCoupangProduct translateAll(Long id) throws Exception {
//初始化翻译所需字符数量
Integer wordCountAll = 0;
// //获取需要翻译的行
LisCoupangProduct coupangProduct = lisCoupangProductMapper.selectById(id);
//翻译名字
Result result = TranslateUtil.translate(coupangProduct.getCoupangProName());
assert result != null;
String translatedName = result.getText();
coupangProduct.setCoupangProName(translatedName);
wordCountAll += Integer.parseInt(result.getNumber());
//翻译sku_pro
JSONArray jsonArray = JSON.parseArray(coupangProduct.getSkuProperty());
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
jsonArray.remove(i);
String attributeTypeName = (String) jsonObject.get("attributeTypeName");
if (!StringUtils.isEmpty(attributeTypeName)) {
Result result1 = TranslateUtil.translate(attributeTypeName);
assert result1 != null;
wordCountAll += Integer.parseInt(result1.getNumber());
String translatedKey = result1.getText();
jsonObject.put("attributeTypeName", translatedKey);
}
JSONArray jsonArray1 = (JSONArray) jsonObject.get("attributeValueName");
for(int j = 0;j<jsonArray1.size();j++){
//获取
String attributeValueName = jsonArray1.getString(j);
jsonArray1.remove(j);
//翻译
if (!StringUtils.isEmpty(attributeValueName)) {
Result result2 = TranslateUtil.translate(attributeValueName);
assert result2 != null;
wordCountAll += Integer.parseInt(result2.getNumber());
String translatedValue = result2.getText();
//添加
jsonArray1.add(j,translatedValue);
}
}
jsonObject.put("attributeValueName", jsonArray1);
jsonArray.add(i, jsonObject);
}
coupangProduct.setSkuProperty(jsonArray.toJSONString());
//翻译product_propertys
JSONArray jsonArray1 = JSON.parseArray(coupangProduct.getProductProperties());
for (int i = 0; i < jsonArray1.size(); i++) {
JSONObject jsonObject = jsonArray1.getJSONObject(i);
jsonArray1.remove(i);
String itemName = (String) jsonObject.get("itemName");
if (!StringUtils.isEmpty(itemName)) {
Result result2 = TranslateUtil.translate(itemName);
wordCountAll += Integer.parseInt(result2.getNumber());
String translatedItem = result2.getText();
jsonObject.put("itemName", translatedItem);
}
String emptyBarcodeReason = (String) jsonObject.get("emptyBarcodeReason");
if (!StringUtils.isEmpty(emptyBarcodeReason)) {
Result result2 = TranslateUtil.translate(emptyBarcodeReason);
wordCountAll += Integer.parseInt(result2.getNumber());
String translatedEmptyBarcodeReason = result2.getText();
jsonObject.put("emptyBarcodeReason", translatedEmptyBarcodeReason);
}
JSONArray jsonArray2 = jsonObject.getJSONArray("searchTags");
if (jsonArray2.size() != 0) {
for (int j = 0; j < jsonArray2.size(); j++) {
Result result2 = TranslateUtil.translate((String) jsonArray2.get(j));
wordCountAll += Integer.parseInt(result2.getNumber());
jsonArray2.remove(j);
jsonArray2.add(j, result2.getText());
}
jsonObject.put("searchTags", jsonArray2);
}
JSONArray jsonArray3 = jsonObject.getJSONArray("attributes");
System.out.println(jsonArray3);
if (jsonArray3.size() != 0) {
for (int m = 0; m < jsonArray3.size(); m++) {
JSONObject jsonObject1 = jsonArray3.getJSONObject(m);
System.out.println(jsonObject1);
jsonArray3.remove(m);
String attributeTypeName = (String) jsonObject1.get("attributeTypeName");
if (!StringUtils.isEmpty(attributeTypeName)) {
Result result1 = TranslateUtil.translate(attributeTypeName);
wordCountAll += Integer.parseInt(result1.getNumber());
String translatedKey = result1.getText();
jsonObject1.put("attributeTypeName", translatedKey);
}
String attributeValueName = (String) jsonObject1.get("attributeValueName");
if (!StringUtils.isEmpty(attributeValueName)) {
Result result2 = TranslateUtil.translate(attributeValueName);
wordCountAll += Integer.parseInt(result2.getNumber());
String translatedValue = result2.getText();
jsonObject1.put("attributeValueName", translatedValue);
}
jsonArray3.add(m, jsonObject1);
}
jsonObject.put("attributes", jsonArray3);
}
String offerDescription = (String) jsonObject.get("offerDescription");
if (!StringUtils.isEmpty(offerDescription)) {
Result result2 = TranslateUtil.translate(offerDescription);
wordCountAll += Integer.parseInt(result2.getNumber());
String translatedOfferDescription = result2.getText();
jsonObject.put("offerDescription", translatedOfferDescription);
}
String extraInfoMessage = (String) jsonObject.get("extraInfoMessage");
if (!StringUtils.isEmpty(extraInfoMessage)) {
Result result2 = TranslateUtil.translate(extraInfoMessage);
wordCountAll += Integer.parseInt(result2.getNumber());
String translatedExtraInfoMessage = result2.getText();
jsonObject.put("extraInfoMessage", translatedExtraInfoMessage);
}
String manufacture = (String) jsonObject.get("manufacture");
if (!StringUtils.isEmpty(manufacture)) {
Result result2 = TranslateUtil.translate(manufacture);
wordCountAll += Integer.parseInt(result2.getNumber());
String translateManufacture = result2.getText();
jsonObject.put("manufacture", translateManufacture);
}
jsonArray1.add(i,jsonObject);
}
coupangProduct.setProductProperties(jsonArray1.toJSONString());
//更新coupangProduct进入数据库.
// lisTranslateMapper.updateTranslated1(id,coupangProduct.getCoupangProName());
// lisTranslateMapper.updateTranslated2(id, coupangProduct.getSkuProperty());
// lisTranslateMapper.updateTranslated3(id, coupangProduct.getProductPropertys());
lisCoupangProductMapper.insertByProduct(coupangProduct);
//把username和wordcount更新进入数据库.
//若不存在则先创建用户
// LisTranslate lisTranslate = lisTranslateMapper.selectUser(username);
// if(lisTranslate==null){
// lisTranslateMapper.creatUser(username, String.valueOf(wordCountAll));
// return coupangProduct ;
// }else {
// Integer word = Integer.getInteger(lisTranslate.getWordcount())+wordCountAll;
// lisTranslateMapper.updateUser(username,String.valueOf(word));
// }
return coupangProduct;
}
//
@Override
public void translate(CoupangProduct product, BaseResult<CoupangProduct> result) {
try {
StringBuilder searchTags = new StringBuilder();
StringBuilder notices = new StringBuilder();
StringBuilder itemName = new StringBuilder();
product.getSearchTags().forEach(s -> {
searchTags.append(s+"#");
});
product.getNotices().forEach(a ->{
notices.append(a+"#");
});
product.getItemName().forEach(n ->{
itemName.append(n+"#");
});
List<String> attributeTypeNameList = product.getAttributeTypeNameList();
List<String> attributeValueNameList = product.getAttributeValueNameList();
StringBuilder attributeTypeName = new StringBuilder();
StringBuilder attributeValueName = new StringBuilder();
attributeTypeNameList.forEach(attributes -> {
attributeTypeName.append(attributes+"#");
});
attributeValueNameList.forEach(attributes -> {
attributeValueName.append(attributes+"#");
});
Result coupangProName = TranslateUtil.translate(product.getCoupangProName());
Result brand = product.getBrand()==null?null:TranslateUtil.translate(product.getBrand());
Result searchTag = searchTags.toString().isEmpty()?null:TranslateUtil.translate(searchTags.toString());
Result itemNames = itemName.toString().isEmpty()?null:TranslateUtil.translate(itemName.toString());
Result barcodeReason = product.getEmptyBarcodeReason()==null?null:TranslateUtil.translate(product.getEmptyBarcodeReason());
Result notice = notices.toString().isEmpty()?null:TranslateUtil.translate(notices.toString());
Result typeName = attributeTypeName.toString().isEmpty()?null:TranslateUtil.translate(attributeTypeName.toString());
Result valueName = attributeValueName.toString().isEmpty()?null:TranslateUtil.translate(attributeValueName.toString());
Integer coupangProNameNum = Integer.parseInt(coupangProName.getNumber());
Integer brandNum = Integer.parseInt(brand==null?"0":brand.getNumber());
Integer searchTagNum = Integer.parseInt(searchTag==null?"0":searchTag.getNumber());
Integer itemNamesNum = Integer.parseInt(itemNames==null?"0":itemNames.getNumber());
Integer barcodeReasonNum = Integer.parseInt(barcodeReason==null?"0":barcodeReason.getNumber());
Integer noticeNum = Integer.parseInt(notice==null?"0":notice.getNumber());
Integer typeNameNum = Integer.parseInt(typeName==null?"0":typeName.getNumber());
Integer valueNameNum = Integer.parseInt(valueName==null?"0":valueName.getNumber());
//文字用量统计总和(后面需要用)
Integer sum = coupangProNameNum+brandNum+searchTagNum+itemNamesNum+barcodeReasonNum+noticeNum+typeNameNum+valueNameNum;
BaseResult<Boolean> dataResult = new BaseResult<>();
tErpUserDataStatisticsService.billingStatistics(2,sum,dataResult);
if(!dataResult.isSuccess()){
result.construct(dataResult.getMessage(),false);
return;
}
CoupangProduct coupangProduct = new CoupangProduct();
coupangProduct.setCoupangProName(coupangProName.getText());
coupangProduct.setBrand(brand==null?null:brand.getText());
coupangProduct.setSearchTags(searchTag==null?null:Arrays.asList((searchTag.getText()).split("#")));
coupangProduct.setItemName(itemNames==null?null:Arrays.asList((itemNames.getText()).split("#")));
coupangProduct.setEmptyBarcodeReason(barcodeReason==null?null:barcodeReason.getText());
coupangProduct.setNotices(notice==null?null:Arrays.asList((notice.getText()).split("#")));
coupangProduct.setAttributeTypeNameList(typeName==null?null:Arrays.asList((typeName.getText()).split("#")));
coupangProduct.setAttributeValueNameList(valueName==null?null:Arrays.asList((valueName.getText()).split("#")));
// CoupangProduct coupangProduct = new CoupangProduct();
// coupangProduct.setCoupangProName(TranslateUtil.translate(product.getCoupangProName()).getText());
// coupangProduct.setBrand(product.getBrand()==null?null:TranslateUtil.translate(product.getBrand()).getText());
// coupangProduct.setSearchTags(searchTags.toString().isEmpty()?null:Arrays.asList((TranslateUtil.translate(searchTags.toString()).getText()).split(",")));
// coupangProduct.setItemName(itemName.toString().isEmpty()?null:Arrays.asList((TranslateUtil.translate(itemName.toString()).getText()).split(",")));
// coupangProduct.setEmptyBarcodeReason(product.getEmptyBarcodeReason()==null?null:TranslateUtil.translate(product.getEmptyBarcodeReason()).getText());
// coupangProduct.setNotices(notices.toString().isEmpty()?null:Arrays.asList((TranslateUtil.translate(notices.toString()).getText()).split("#")));
// coupangProduct.setAttributeTypeNameList(attributeTypeName.toString().isEmpty()?null:Arrays.asList((TranslateUtil.translate(attributeTypeName.toString()).getText()).split(",")));
// coupangProduct.setAttributeValueNameList(attributeValueName.toString().isEmpty()?null:Arrays.asList((TranslateUtil.translate(attributeValueName.toString()).getText()).split(",")));
result.construct("成功",true,coupangProduct);
}catch (Exception e){
e.printStackTrace();
}catch (Throwable t){
t.printStackTrace();
}
}
}
涉及到json疯狂嵌套的问题,又不能直接全翻译了 那样会导致浪费.所以就解析着翻译.
然后存入新的表中.
通过原表中的字段表征是否被翻译过,避免重复翻译.
通用版和专业版都对接了.基本没啥区别,就是调接口的时候参数不一样.
1.4 aop搭建全局异常处理与日志系统:
使用自定义注解.
package com.elevator.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 记录接口调用情况注解
* @Author A1yCE
* @Date 2023-11-26 15:22
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiCallLog {
}
然后在每一模块内定义逻辑.
package com.elevator.unit.aop;
import com.elevator.common.utils.UserUtils;
import com.elevator.system.client.SystemClient;
import com.elevator.system.dto.BaseResponseDTO;
import com.elevator.system.dto.UserDto;
import com.elevator.unit.entity.ApiCallLog;
import com.elevator.unit.repository.ApiCallLogRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author A1yCE
* @Date 2023-11-26 15:23
**/
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class ApiAdvice {
private final SystemClient systemClient;
private final ApiCallLogRepository apiCallLogRepository;
@Pointcut("@annotation(com.elevator.common.annotation.ApiCallLog)")
private void apiAdvicePointCut(){}
@Before("apiAdvicePointCut()")
public void apiAdvice(){
try {
ApiCallLog apiCallLog = new ApiCallLog();
Long userId = UserUtils.getUserId(); // 用户id
String unitType = UserUtils.getUnitType(); // 单位名称
BaseResponseDTO<UserDto> userDto = systemClient.findUserById(userId);
String username = userDto.getData().getUsername(); // 账号
String nickName = userDto.getData().getNickName(); // 真名
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String operation = attributes.getRequest().getRequestURI(); // 操作的接口
List<String> roleIds = UserUtils.getRoleIds();
List<Long> idList = roleIds.stream().map(Long::new).collect(Collectors.toList());
BaseResponseDTO<List<String>> roleListDto = systemClient.findNameByIdList(idList);
List<String> roleList = roleListDto.getData();
String role = String.join(",",roleList); // 角色
apiCallLog.setUserId(userId);
apiCallLog.setUserName(username);
apiCallLog.setNickName(nickName);
apiCallLog.setRole(role);
apiCallLog.setOperation(operation);
apiCallLog.setUnitType(unitType);
apiCallLogRepository.save(apiCallLog);
} catch (Exception e) {
log.error("服务器内部错误");
}
}
}
切面(Aspect): 切面是横切关注点的模块化单元。它定义了在何处以及如何应用横切关注点。切面包含了通知(Advice)和切点(Pointcut)。
通知(Advice): 通知是切面的具体行为。它定义了在切点处执行的代码。AOP有几种类型的通知,包括前置通知(Before Advice)、后置通知(After Advice)、返回通知(After Returning Advice)、异常通知(After Throwing Advice)和环绕通知(Around Advice)。
前置通知(Before Advice): 在切点之前执行的通知。
后置通知(After Advice): 在切点之后执行的通知,不考虑方法的成功或失败。
返回通知(After Returning Advice): 在切点方法成功返回后执行的通知。
异常通知(After Throwing Advice): 在切点方法抛出异常后执行的通知。
环绕通知(Around Advice): 包围切点方法的通知,可以在方法调用前后执行自定义的行为。
切点(Pointcut): 切点定义了在应用通知的位置。它是一个表达式,描述了哪些连接点(Join Points)会触发通知。连接点是程序执行的点,例如方法调用、异常抛出等。
连接点(Join Point): 连接点是在应用程序执行过程中可以插入切面的点。例如,方法的调用是一个连接点。
织入(Weaving): 织入是将切面应用到目标对象并创建一个新的代理对象的过程。有三种织入方式:编译时织入(Compile-Time Weaving)、类加载时织入(Load-Time Weaving)和运行时织入(Runtime Weaving)。
编译时织入: 在编译时将切面织入目标代码,生成最终的字节码文件。
类加载时织入: 在类加载到JVM时,通过类加载器将切面织入目标类。
运行时织入: 在应用程序运行时,通过特定的代理机制将切面织入目标对象
使用注解定义切点的位置.
然后执行一个前置通知处理日志系统.通过解析jwt获取id,然后把相关参数存入数据库.
然后执行一个环绕通知处理异常,发生异常时,直接返回前端相应的信息,然后同样执行日志存储的逻辑.
1.5 线程池订单数据:
1.业务场景:订单写入
主要还涉及到一些数据处理和翻译.
2.线程池:
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;
只要有并发的地方、任务数量大或小、每个任务执行时间长或短的都可以使用线程池;
总体来说,线程池有如下的优势:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建固定大小的线程池,线程数为5
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务给线程池
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Executing task: " + taskId + " on thread " + Thread.currentThread().getName());
});
}
// 关闭线程池
executorService.shutdown();
}
}
1.6 分布式定时任务:
刷本系统内的库存信息:就是把国内平台采集的库存数同步到韩国数据表中.
目前是单机,但是后续有分布式需求.
分布式定时任务是指在一个分布式系统中,多个节点(服务器或服务实例)协同合作执行定时任务的一种机制。在传统的单体应用中,定时任务通常是在单一的应用实例中运行的,但在分布式系统中,存在多个独立的节点,每个节点都可能需要执行定时任务。
使用redis分布式锁实现.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class DistributedLock {
private static final String LOCK_KEY = "my_task_lock";
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean tryLock() {
return redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "locked");
}
public void releaseLock() {
redisTemplate.delete(LOCK_KEY);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTask {
@Autowired
private DistributedLock distributedLock;
@Scheduled(cron = "0/30 * * * * *") // 每30秒执行一次
public void myTask() {
// 尝试获取分布式锁
if (distributedLock.tryLock()) {
try {
// 执行定时任务的逻辑
System.out.println("定时任务执行时间:" + System.currentTimeMillis());
} finally {
// 释放分布式锁
distributedLock.releaseLock();
}
} else {
// 未获取到锁,说明有其他实例在执行任务
System.out.println("未获取到分布式锁,任务未执行");
}
}
}
1.7 统计报表:
选择日期.
统计订单信息,金额信息,运费信息,物流信息等等.
这些信息都是已经在数据库中存好了的.只是需要进一步处理.
使用了mybatis的动态sql技术.
demo:
public class User {
private Long id;
private String username;
private String email;
// 省略其他属性、构造方法和 getter/setter
}
import java.util.List;
public interface UserMapper {
List<User> getUserByCondition(User user);
}
<!-- resources/mapper/UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserByCondition" resultType="com.example.model.User">
SELECT * FROM user
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
</select>
</mapper>
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.List;
@Configuration
public class MyBatisConfig {
@Autowired
private DataSource dataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean.getObject();
}
@Autowired
private UserMapper userMapper;
public void exampleMethod() {
User queryUser = new User();
queryUser.setUsername("testUser");
List<User> users = userMapper.getUserByCondition(queryUser);
// 处理查询结果
}
}
持久化的优势:
历史数据分析: 如果你需要对历史数据进行分析或者生成历史趋势报表,持久化数据是很有帮助的。
性能优化: 对于一些复杂的统计计算,将结果持久化到数据库中可以减少重复计算的成本,提高查询性能。
报表的长期保存: 如果需要长期保存报表数据,而不仅仅是即时查询,那么将统计结果持久化到数据库中是比较常见的做法。
不持久化的优势:
实时性: 如果你的报表需要实时展示最新数据,而不是历史数据,那么不持久化可能更适合。
减少存储空间: 持久化数据会占用存储空间,如果数据的增长速度较快,而且不需要长期保存历史数据,可以考虑不持久化,只在需要时计算。
计算成本: 有时候,统计计算的成本可能较高,但是如果数据更新频繁,而报表查询并不频繁,那么实时计算可能更经济。
综合考虑:
业务需求: 确保你了解业务需求,如果业务需要展示历史趋势或者需要在报表中提供历史数据分析,那么持久化可能是有必要的。
性能需求: 如果数据量大、查询频繁,而且性能是关键因素,可能需要考虑持久化。
存储成本: 考虑存储成本和数据增长的速度,以及是否有必要长期保存历史数据。
实时性需求: 如果实时性是关键需求,而且计算成本可以接受,可以选择不持久化。
对于订单的金额相关信息,以整月的形式持久化.
对于其他的乱七八糟的信息,则现场查.
根据日期的查询 能查出来说明是持久化条件. 查不出来说明是不持久化的日期,那么就现场查,可能需要等一会.
1.8 项目工期把控与进度管理:
1.评估:
老师给出项目文档,分享需求,评估一下需要找多少人做,几个人大三的,几个大二的这样.
2.需求会:
我们和甲方都去实验室线下.跟甲方开需求会.明确需求,讲解需求.共同指定工期.
3.文档把控:
原先的方式是每个人每天在群里汇报:
昨日的总结
今日的工作
但是这种方式存在着很多的问题,因为学校这个情况,大部分人都是开发时间不均衡的.
所以变成文档的形式.
编写文档,包括谁 什么时间 什么内容 开发情况 对接情况 遇到的问题等 每个人每周一般是填一次.
然后负责人通过看文档,找到问题并联系相关同学解决.
2.电梯:
2.1 项目来源:
层层外包.
威海市政府原有电梯维保项目,但是不能满足日渐增加的需求,所以招标重构项目.
威海市广日电梯有限公司接到项目,然后外包给若维,然后若维的leader是我们学校的毕业生,再跟我们实验室共同开发.
负责管理电梯的方方面面,核心是三大工单,维保,救援,报修的业务逻辑,还有各种单位的互相交互,使用单位,维保单位,救援单位,应急处置单位,等.还有大屏展示等功能.
2.2 项目架构:
确定技术栈.
springcloud nacos gateway openfegin
确定用户认证体系.
springsecruity oauth2 :集成度高.方便扩展.
确定微服务模块划分.
微服务模块划分是设计和组织一个微服务架构时的关键步骤之一。
以下是一些通用的微服务模块划分原则:
单一职责原则(Single Responsibility Principle):
每个微服务应该专注于一个特定的业务功能或业务流程,执行特定的业务操作。这有助于确保服务的职责清晰,易于维护和理解。
松耦合(Loose Coupling):
微服务之间应该尽量减少相互依赖,避免紧密耦合。松耦合使得单个服务的修改不会对其他服务产生过多的影响,提高了系统的灵活性和可维护性。
高内聚(High Cohesion):
一个微服务内的组件应该紧密相关,共同实现特定的业务功能。高内聚有助于确保服务的功能单一且容易维护。
界限上下文(Bounded Context):
根据领域驱动设计(Domain-Driven Design,DDD)的原则,微服务的划分应该基于界限上下文,即每个微服务应该有明确定义的业务边界,服务内的模型和术语应该在该边界内保持一致。
可独立部署(Independent Deployment):
每个微服务应该是独立可部署的单元。这意味着修改一个服务不应该影响其他服务,使得团队能够独立地开发、测试、部署和升级各个服务。
可伸缩性(Scalability):
考虑到系统可能的未来需求,微服务的划分应该有助于系统的横向扩展。这意味着可以根据需要增加特定服务的实例数量,而不影响整体系统的性能。
通信开销最小化:
服务之间的通信会引入一定的开销,因此应该尽量减少服务之间的通信量。可以采用事件驱动、异步通信等方式来降低直接同步调用的频率。
数据管理独立性:
每个微服务应该有自己的数据存储,并通过 API 提供对数据的访问。避免直接访问其他服务的数据库,以保持数据管理的独立性。
安全性和隔离性:
考虑到微服务架构中的多个服务可能面临不同的安全需求,确保每个服务都有适当的安全措施。此外,服务之间应该有足够的隔离,以防止故障在整个系统中传播。
尽量减少模块之间的数据库调用.
尽量减少各个业务跨模块的调用.
确定人员分配.
2.3 CI/CD:
持续集成(Continuous Integration) 定义:持续频繁的(每天多次)将本地代码“集成”到主干分支,并保证主干分支可用
如果只做到持续集成的话其实没啥用,这个是通过规范实现的.
1.从git上拉取项目 git clone http://ruoweiedu.com:7000/elevator/elevator-backend.git;
2.创建本地开发分支:git checkout -b xxx_dev (xxx一般为姓名首字母小写,如hjm_dev);
3.每次写代码前,都要更新本地代码和master保持一直:git pull origin master;
4.代码提交需先push到自己的开发分支上:git add --all && git commit -m '日志信息' && git push origin xxx_dev;
5.手动将xxx_dev 合并到master分支,点击左侧合并请求合并分支到master
每个人在gitlab上有一个自己的分支.
进行代码开发完成后, add commit 到自己仓库 然后同步到远程仓库,然后手动合并分支到自己的仓库.并进行本地测试后.
这样就实现了基本的ci/cd流程.
持续交付(Continuous Delivery) 定义:是持续集成的下一步,持续频繁地将软件的新版本交付到类生产环境(类似于预发),交付给测试、产品验收。
持续部署(Continuous Deployment) 定义:是持续交付的下一步,“自动”将代码部署到生产环境
在这个项目里,开发阶段的生产服务器和测试服务器都是同一个,所以这两个其实是同一个概念.
而这一步是通过jenkins实现的.
具体来说就是,当master被合并时,唤醒gitlab hook,启动jenkins.
jenkins配置了一个简单风格的项目(性能原因)
源码为gitlab地址
触发器为gitlab hook
构建其实是不做任何操作的.
构建后操作 使用ssh连接服务器 上传最新的文件
并运行服务器上的部署脚本
在服务器上进行mvn打包 进程关闭 进程重启操作.
这样会出现 删除文件问题 目前没啥办法
还有自动化测试的问题.
apifox这个软件可以管理接口信息和测试用例,适用于本地测试.
还可以使用idea插件自动的生成测试文档,避免了代码侵入性.
还有就是下面要讲的这个测试软件andulir.
2.4 自动化迁移:
原有的数据库是在postgres,新系统因为国产化需求需要进行数据库的迁移.
主要是一个shell 脚本 一个python脚本 一个java脚本实现的.
需求:按列迁移.
方案: 首先给到的是一整个数据库的备份
python脚本切分文件
找到想要的表
java改变文件格式
导入postgers
shell启动达梦 最后一步需要手动迁移.
2.5 外部对接方案:
电梯保险数据接收.
工单写入
电梯运行状态等
安全性:appkey secret
幂等性: redis自增函数获取唯一的操作id
携带id访问内部接口
分布式锁.保证幂等性.
2.6 部署:
Kubernetes(K8s)是一个开源的容器编排平台,用于自动化容器化应用程序的部署、扩展和运维。以下是使用Kubernetes部署项目的一般流程:
准备工作:
安装和配置kubectl:kubectl是与Kubernetes集群进行交互的命令行工具。你需要安装并配置kubectl,以便连接到你的Kubernetes集群。
安装和配置Kubernetes集群:你可以选择使用各种工具,如Minikube(本地开发和测试)、kubeadm、或云服务提供商的托管Kubernetes集群。
容器化应用:
将你的应用程序容器化:使用Docker或其他容器化工具将你的应用程序打包为Docker镜像。确保Docker镜像包含应用程序的所有依赖项。
创建Kubernetes配置文件:
编写Kubernetes配置文件(YAML格式):定义Deployment、Service、ConfigMap等Kubernetes资源的配置。配置文件描述了你的应用程序的部署和服务配置。
部署应用程序:
使用kubectl apply命令应用你的Kubernetes配置文件:这将在Kubernetes集群中创建相应的资源(Deployment、Service等)来部署和运行你的应用程序。
bashCopy codekubectl apply -f your-deployment.yaml
kubectl apply -f your-service.yaml
2.7 websocket rocket mq:
工单完成通知 order模块通知 unit模块
大屏展示功能 通过socket实现工单监控数据的变化.
3.Andulir:
在项目实践中开发的开源项目.
用于本地测试和项目上线的自动化测试.
使用流程:
引入
注解
启动项目
修改xml配置
重新启动
架构:
数据解析器:通过注解搜索到方法 生成对应的xml结构 持久化为一个xml文件.
数据生成器:根据xml文件 随机的生成测试数据
测试工具:
CommandLineRunner
是 Spring 框架中用于构建命令行应用程序的接口。它属于 org.springframework.boot
包,提供了在 Spring Boot 应用程序启动时运行代码的方式。该接口只包含一个方法:run
@Autowired
private ApplicationContext applicationContext;
测试的时候通过反射生成的对象不能注入 所以要通过上面的方法获取bean.
然后还有 git diff 命令 openfegin 前端 自动化测试. 管理测试用例的概念.
计划为其设计golang版本.