Compare commits
8 Commits
czleilei24
...
sqszx202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdabe3ba95 | ||
|
|
4d12a49f7c | ||
|
|
c3f2494243 | ||
|
|
01e373faf6 | ||
|
|
6b940e424c | ||
|
|
5762f4e762 | ||
|
|
718d8c5a3c | ||
|
|
9eac385378 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -37,3 +37,8 @@ deploy/docker/scripts/server.env
|
||||
deploy/docker/step1-integral/.env
|
||||
deploy/docker/step1-integral/houtai.env
|
||||
deploy/docker/step2-single-shop/.env
|
||||
|
||||
# Local migration backups and generated Python cache
|
||||
docs/sql/backups/
|
||||
__pycache__/
|
||||
**/__pycache__/
|
||||
|
||||
@@ -13,8 +13,8 @@ ENV = 'development'
|
||||
# shjjy153 项目
|
||||
# VUE_APP_BASE_API = 'http://jjy-jfadmin.fwxgpt.com'
|
||||
|
||||
# czleilei240 项目
|
||||
VUE_APP_BASE_API = 'https://leilei-jf.czchunfang.com'
|
||||
# sqszx202 项目
|
||||
VUE_APP_BASE_API = 'https://jf.j3s4s5.com'
|
||||
|
||||
# hapr191 项目(淮安鹏然商贸)
|
||||
# VUE_APP_BASE_API = 'http://jfadmin.hapengran.com'
|
||||
|
||||
@@ -13,8 +13,8 @@ ENV = 'production'
|
||||
# shjjy153 项目
|
||||
# VUE_APP_BASE_API = 'http://jjy-jfadmin.fwxgpt.com'
|
||||
|
||||
# czleilei240 项目
|
||||
VUE_APP_BASE_API = 'https://leilei-jf.czchunfang.com'
|
||||
# sqszx202 项目
|
||||
VUE_APP_BASE_API = 'https://jf.j3s4s5.com'
|
||||
|
||||
# hapr191 项目(淮安鹏然商贸)
|
||||
# VUE_APP_BASE_API = 'http://jfadmin.hapengran.com'
|
||||
|
||||
@@ -50,7 +50,7 @@ public class UploadController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,news文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> image(MultipartFile multipart,
|
||||
public CommonResult<FileResultVo> image(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
return CommonResult.success(uploadService.imageUpload(multipart, model, pid));
|
||||
@@ -66,7 +66,7 @@ public class UploadController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,news文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> file(MultipartFile multipart,
|
||||
public CommonResult<FileResultVo> file(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
return CommonResult.success(uploadService.fileUpload(multipart, model, pid));
|
||||
@@ -75,4 +75,3 @@ public class UploadController {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# CRMEB 相关配置
|
||||
crmeb:
|
||||
captchaOn: false # 是否开启行为验证码
|
||||
asyncConfig: true #是否同步config表数据到redis
|
||||
|
||||
server:
|
||||
port: 30032
|
||||
|
||||
# 订单同步配置(每个单商户实例需要配置不同的source-id和target-mer-id)
|
||||
sync:
|
||||
source-id: shop_16
|
||||
target-mer-id: 16
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
name: byhlc112
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com:3306/${spring.datasource.name}?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
redis:
|
||||
host: 39.97.236.112 #地址
|
||||
port: 6379 #端口
|
||||
password: '123456'
|
||||
timeout: 10000 # 连接超时时间(毫秒)
|
||||
database: 2 #默认数据库
|
||||
jedis:
|
||||
pool:
|
||||
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
|
||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-idle: 10 # 连接池中的最大空闲连接
|
||||
min-idle: 0 # 连接池中的最小空闲连接
|
||||
time-between-eviction-runs: -1 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
|
||||
second:
|
||||
database: 2 # 微信accessToken存储库
|
||||
|
||||
debug: true
|
||||
logging:
|
||||
level:
|
||||
io.swagger.*: error
|
||||
com.zbjk.crmeb: debug
|
||||
org.springframework.boot.autoconfigure: ERROR
|
||||
config: classpath:logback-spring.xml
|
||||
file:
|
||||
path: ./crmeb_log
|
||||
|
||||
# mybatis 配置
|
||||
mybatis-plus:
|
||||
# 配置sql打印日志
|
||||
configuration:
|
||||
log-impl:
|
||||
|
||||
#swagger 配置
|
||||
swagger:
|
||||
basic:
|
||||
enable: true #是否开启界面
|
||||
check: false #是否打开验证
|
||||
username: crmeb #访问swagger的账号
|
||||
password: crmeb.com #访问swagger的密码
|
||||
@@ -0,0 +1,60 @@
|
||||
# CRMEB 相关配置
|
||||
crmeb:
|
||||
captchaOn: false # 是否开启行为验证码
|
||||
asyncConfig: true #是否同步config表数据到redis
|
||||
|
||||
server:
|
||||
port: 30032
|
||||
|
||||
# 订单同步配置(每个单商户实例需要配置不同的source-id和target-mer-id)
|
||||
sync:
|
||||
source-id: shop_17
|
||||
target-mer-id: 17
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
name: sqszx202
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com:3306/${spring.datasource.name}?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
redis:
|
||||
host: 59.110.91.202 #地址
|
||||
port: 6379 #端口
|
||||
password: '123456'
|
||||
timeout: 10000 # 连接超时时间(毫秒)
|
||||
database: 2 #默认数据库
|
||||
jedis:
|
||||
pool:
|
||||
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
|
||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-idle: 10 # 连接池中的最大空闲连接
|
||||
min-idle: 0 # 连接池中的最小空闲连接
|
||||
time-between-eviction-runs: -1 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
|
||||
second:
|
||||
database: 2 # 微信accessToken存储库
|
||||
|
||||
debug: true
|
||||
logging:
|
||||
level:
|
||||
io.swagger.*: error
|
||||
com.zbjk.crmeb: debug
|
||||
org.springframework.boot.autoconfigure: ERROR
|
||||
config: classpath:logback-spring.xml
|
||||
file:
|
||||
path: ./crmeb_log
|
||||
|
||||
# mybatis 配置
|
||||
mybatis-plus:
|
||||
# 配置sql打印日志
|
||||
configuration:
|
||||
log-impl:
|
||||
|
||||
#swagger 配置
|
||||
swagger:
|
||||
basic:
|
||||
enable: true #是否开启界面
|
||||
check: false #是否打开验证
|
||||
username: crmeb #访问swagger的账号
|
||||
password: crmeb.com #访问swagger的密码
|
||||
@@ -38,7 +38,7 @@ server:
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: czleilei240
|
||||
active: sqszx202
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 50MB #设置单个文件大小
|
||||
@@ -172,4 +172,4 @@ aj:
|
||||
# local定时清除过期缓存(单位秒),设置为0代表不执行
|
||||
timing-clear: 3600
|
||||
|
||||
history-data-clear-enable: false
|
||||
history-data-clear-enable: false
|
||||
|
||||
@@ -48,11 +48,11 @@ public class UploadFrontController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,news文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> image(MultipartFile multipart, @RequestParam(value = "model") String model,
|
||||
public CommonResult<FileResultVo> image(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
return CommonResult.success(uploadService.imageUpload(multipart, model, pid));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public class UserUploadController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,news文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> image(MultipartFile multipart,
|
||||
public CommonResult<FileResultVo> image(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
|
||||
@@ -66,9 +66,9 @@ public class UserUploadController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,news文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> imageOuter(MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
public CommonResult<FileResultVo> imageOuter(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
|
||||
return CommonResult.success(uploadService.imageUpload(multipart, model, pid));
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public class UserUploadController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,news文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> file(MultipartFile multipart,
|
||||
public CommonResult<FileResultVo> file(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
return CommonResult.success(uploadService.fileUpload(multipart, model, pid));
|
||||
@@ -92,4 +92,3 @@ public class UserUploadController {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.zbkj.common.response.WaLoginResponse;
|
||||
import com.zbkj.common.response.WaUserInfoResponse;
|
||||
import com.zbkj.common.result.CommonResult;
|
||||
import com.zbkj.common.token.FrontTokenComponent;
|
||||
import com.zbkj.common.config.CrmebConfig;
|
||||
import com.zbkj.common.vo.FileResultVo;
|
||||
import com.zbkj.front.service.WaUserService;
|
||||
import com.zbkj.service.dao.consignment.WaUsersDao;
|
||||
@@ -14,7 +15,6 @@ import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
@@ -22,6 +22,9 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -44,12 +47,13 @@ import java.util.Date;
|
||||
* |
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("api/front/wa/user")
|
||||
@Api(tags = "寄卖服务 -- 用户认证")
|
||||
public class WaUserController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WaUserController.class);
|
||||
|
||||
@Autowired
|
||||
private WaUserService waUserService;
|
||||
|
||||
@@ -62,6 +66,17 @@ public class WaUserController {
|
||||
@Autowired
|
||||
private WaUsersDao waUsersDao;
|
||||
|
||||
@Autowired
|
||||
private CrmebConfig crmebConfig;
|
||||
|
||||
private String buildPublicFileUrl(String relativeUrl) {
|
||||
String domain = StringUtils.defaultString(crmebConfig.getDomain(), "https://j3s4s5.com").trim();
|
||||
if (!StringUtils.startsWithAny(domain, "http://", "https://")) {
|
||||
domain = "https://" + domain;
|
||||
}
|
||||
return StringUtils.removeEnd(domain, "/") + "/" + StringUtils.removeStart(relativeUrl, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理PDF文件,添加用户签名和签署日期
|
||||
* @param signatureImage 用户签名图片
|
||||
@@ -73,7 +88,7 @@ public class WaUserController {
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
// 读取模板PDF文件
|
||||
Resource resource = new ClassPathResource("pdf/sign_contract_czleilei240.pdf");
|
||||
Resource resource = new ClassPathResource("pdf/sign_contract_sqszx202.pdf");
|
||||
InputStream pdfInputStream = resource.getInputStream();
|
||||
document = PDDocument.load(pdfInputStream);
|
||||
pdfInputStream.close();
|
||||
@@ -185,21 +200,27 @@ public class WaUserController {
|
||||
@ApiImplicitParam(name = "model", value = "模块 用户user,商品product,微信wechat,新闻文章"),
|
||||
@ApiImplicitParam(name = "pid", value = "分类ID 0编辑器,1商品图片,2拼团图片,3砍价图片,4秒杀图片,5文章图片,6组合数据图,7前台用户,8微信系列 ", allowableValues = "range[0,1,2,3,4,5,6,7,8]")
|
||||
})
|
||||
public CommonResult<FileResultVo> image(MultipartFile multipart, @RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid") Integer pid) throws IOException {
|
||||
public CommonResult<FileResultVo> image(@RequestParam("multipart") MultipartFile multipart,
|
||||
@RequestParam(value = "model") String model,
|
||||
@RequestParam(value = "pid", required = false) Integer pid,
|
||||
@RequestParam(value = "userId", required = false) Integer userId) throws IOException {
|
||||
// 如果是用户模型且上传的是图片,则先处理PDF文件
|
||||
if ("user".equals(model) && multipart != null) {
|
||||
// 处理PDF文件,添加用户签名和签署日期
|
||||
FileResultVo pdfResultVo = processPdfWithSignature(multipart);
|
||||
if (pdfResultVo != null) {
|
||||
// 更新用户contract字段
|
||||
if (pid != null) {
|
||||
Integer targetUserId = userId != null ? userId : pid;
|
||||
if (targetUserId == null) {
|
||||
targetUserId = frontTokenComponent.getUserId();
|
||||
}
|
||||
if (targetUserId != null) {
|
||||
WaUsers user = new WaUsers();
|
||||
user.setId(pid);
|
||||
user.setId(targetUserId);
|
||||
// user.setContract("https://anyue.szxingming.com/"+pdfResultVo.getUrl());
|
||||
// user.setContract("https://xiashengjun.com/"+pdfResultVo.getUrl());
|
||||
// user.setContract("https://ccd.cichude.com/"+pdfResultVo.getUrl());
|
||||
user.setContract("https://leilei.czchunfang.com/"+pdfResultVo.getUrl());
|
||||
user.setContract(buildPublicFileUrl(pdfResultVo.getUrl()));
|
||||
waUsersDao.updateById(user);
|
||||
}
|
||||
return CommonResult.success(pdfResultVo);
|
||||
@@ -265,4 +286,4 @@ public class WaUserController {
|
||||
public CommonResult<Boolean> changePassword(@RequestBody @Validated PasswordRequest request) {
|
||||
return CommonResult.success(waUserService.changePassword(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
crmeb:
|
||||
imagePath: /www/wwwroot/h5y2c.com/ # 服务器图片路径配置 斜杠结尾
|
||||
domain: h5y2c.com # 当前项目域名,合同/PDF 等公开地址拼接使用
|
||||
asyncConfig: true #是否同步config表数据到redis
|
||||
|
||||
server:
|
||||
port: 30031
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
name: byhlc112
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com:3306/${spring.datasource.name}?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
redis:
|
||||
host: 39.97.236.112 #地址
|
||||
port: 6379 #端口
|
||||
password: '123456'
|
||||
timeout: 10000 # 连接超时时间(毫秒)
|
||||
database: 2 #默认数据库
|
||||
jedis:
|
||||
pool:
|
||||
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
|
||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-idle: 10 # 连接池中的最大空闲连接
|
||||
min-idle: 0 # 连接池中的最小空闲连接
|
||||
time-between-eviction-runs: -1 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
|
||||
second:
|
||||
database: 2 # 微信accessToken存储库
|
||||
|
||||
debug: true
|
||||
logging:
|
||||
level:
|
||||
io.swagger.*: error
|
||||
com.zbjk.crmeb: debug
|
||||
org.springframework.boot.autoconfigure: ERROR
|
||||
config: classpath:logback-spring.xml
|
||||
file:
|
||||
path: ./logs
|
||||
|
||||
# mybatis 配置
|
||||
mybatis-plus:
|
||||
# 配置sql打印日志
|
||||
configuration:
|
||||
log-impl:
|
||||
|
||||
#swagger 配置
|
||||
swagger:
|
||||
basic:
|
||||
enable: true #是否开启界面
|
||||
check: false #是否打开验证
|
||||
username: crmeb #访问swagger的账号
|
||||
password: crmeb.com #访问swagger的密码
|
||||
@@ -0,0 +1,55 @@
|
||||
crmeb:
|
||||
imagePath: /www/wwwroot/j3s4s5.com/ # 服务器图片路径配置 斜杠结尾
|
||||
domain: https://j3s4s5.com/ # 当前项目域名,合同/PDF 等公开地址拼接使用
|
||||
asyncConfig: true #是否同步config表数据到redis
|
||||
|
||||
server:
|
||||
port: 30031
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
name: sqszx202
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com:3306/${spring.datasource.name}?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
redis:
|
||||
host: 59.110.91.202 #地址
|
||||
port: 6379 #端口
|
||||
password: '123456'
|
||||
timeout: 10000 # 连接超时时间(毫秒)
|
||||
database: 2 #默认数据库
|
||||
jedis:
|
||||
pool:
|
||||
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
|
||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-idle: 10 # 连接池中的最大空闲连接
|
||||
min-idle: 0 # 连接池中的最小空闲连接
|
||||
time-between-eviction-runs: -1 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
|
||||
second:
|
||||
database: 2 # 微信accessToken存储库
|
||||
|
||||
debug: true
|
||||
logging:
|
||||
level:
|
||||
io.swagger.*: error
|
||||
com.zbjk.crmeb: debug
|
||||
org.springframework.boot.autoconfigure: ERROR
|
||||
config: classpath:logback-spring.xml
|
||||
file:
|
||||
path: ./logs
|
||||
|
||||
# mybatis 配置
|
||||
mybatis-plus:
|
||||
# 配置sql打印日志
|
||||
configuration:
|
||||
log-impl:
|
||||
|
||||
#swagger 配置
|
||||
swagger:
|
||||
basic:
|
||||
enable: true #是否开启界面
|
||||
check: false #是否打开验证
|
||||
username: crmeb #访问swagger的账号
|
||||
password: crmeb.com #访问swagger的密码
|
||||
@@ -32,7 +32,7 @@ server:
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: czleilei240
|
||||
active: sqszx202
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 50MB #设置单个文件大小
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,13 @@
|
||||
|
||||
> 详细方案见仓库根目录的 `DOCKER_DEPLOY.md`。本文件只列必要操作。
|
||||
|
||||
## 已提供的项目化部署目录
|
||||
|
||||
| 项目 | 步骤一 | 步骤二 |
|
||||
|-----|------|------|
|
||||
| `byhlc112` | `deploy/docker/step1-integral-byhlc112` | `deploy/docker/step2-single-shop-byhlc112` |
|
||||
| `bygsf212`(鼎信汇商贸) | 待步骤一项目目录 | `deploy/docker/step2-single-shop-bygsf212` |
|
||||
|
||||
## 1. 准备环境变量
|
||||
|
||||
```bash
|
||||
@@ -68,7 +75,8 @@ done
|
||||
|
||||
## 7. "fast" 模式(跳过前端构建,使用源码已有 dist)
|
||||
|
||||
如果源码目录里 `backend-adminend/dist` 和 `single_uniapp22miao/unpackage/dist/build/` 已经是最新构建产物,可加速:
|
||||
- **手动使用HBuilder编译发布**
|
||||
- 如果源码目录里 `backend-adminend/dist` 和 `single_uniapp22miao/unpackage/dist/build/` 已经是最新构建产物,可加速:
|
||||
|
||||
```bash
|
||||
docker compose build --build-arg=BUILDKIT_INLINE_CACHE=1 \
|
||||
@@ -110,4 +118,3 @@ ssh-copy-id root@116.62.83.240
|
||||
|
||||
> 用密码模式需要先 `brew install hudochenkov/sshpass/sshpass`(macOS)。
|
||||
> 用 SSH key 模式则任何依赖都不需要。
|
||||
|
||||
|
||||
54
deploy/docker/nginx/admin.lehoo6.com.conf
Normal file
54
deploy/docker/nginx/admin.lehoo6.com.conf
Normal file
@@ -0,0 +1,54 @@
|
||||
upstream resell_api {
|
||||
server 127.0.0.1:18085;
|
||||
keepalive 10240;
|
||||
}
|
||||
|
||||
server
|
||||
{
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
server_name admin.lehoo6.com;
|
||||
index index.html index.htm default.htm default.html;
|
||||
root /www/wwwroot/admin.lehoo6.com;
|
||||
include /www/server/panel/vhost/nginx/extension/admin.lehoo6.com/*.conf;
|
||||
#CERT-APPLY-CHECK--START
|
||||
include /www/server/panel/vhost/nginx/well-known/admin.lehoo6.com.conf;
|
||||
#CERT-APPLY-CHECK--END
|
||||
|
||||
#SSL-START
|
||||
set $isRedcert 1;
|
||||
if ($server_port != 443) {
|
||||
set $isRedcert 2;
|
||||
}
|
||||
if ( $uri ~ /\.well-known/ ) {
|
||||
set $isRedcert 1;
|
||||
}
|
||||
if ($isRedcert != 1) {
|
||||
rewrite ^(/.*)$ https://$host$1 permanent;
|
||||
}
|
||||
ssl_certificate /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.pem;
|
||||
ssl_certificate_key /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.key;
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_tickets on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
error_page 497 https://$host$request_uri;
|
||||
#SSL-END
|
||||
|
||||
location / {
|
||||
proxy_pass http://resell_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
access_log /www/wwwlogs/admin.lehoo6.com.log;
|
||||
error_log /www/wwwlogs/admin.lehoo6.com.error.log;
|
||||
}
|
||||
92
deploy/docker/nginx/jf.lehoo6.com.conf
Normal file
92
deploy/docker/nginx/jf.lehoo6.com.conf
Normal file
@@ -0,0 +1,92 @@
|
||||
upstream jifenmall_h5 {
|
||||
server 127.0.0.1:18082;
|
||||
keepalive 10240;
|
||||
}
|
||||
|
||||
server
|
||||
{
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
server_name jf.lehoo6.com;
|
||||
index index.html index.htm default.htm default.html;
|
||||
root /www/wwwroot/jf.lehoo6.com;
|
||||
include /www/server/panel/vhost/nginx/extension/jf.lehoo6.com/*.conf;
|
||||
#CERT-APPLY-CHECK--START
|
||||
include /www/server/panel/vhost/nginx/well-known/jf.lehoo6.com.conf;
|
||||
#CERT-APPLY-CHECK--END
|
||||
|
||||
#SSL-START
|
||||
set $isRedcert 1;
|
||||
if ($server_port != 443) {
|
||||
set $isRedcert 2;
|
||||
}
|
||||
if ( $uri ~ /\.well-known/ ) {
|
||||
set $isRedcert 1;
|
||||
}
|
||||
if ($isRedcert != 1) {
|
||||
rewrite ^(/.*)$ https://$host$1 permanent;
|
||||
}
|
||||
ssl_certificate /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.pem;
|
||||
ssl_certificate_key /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.key;
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_tickets on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
error_page 497 https://$host$request_uri;
|
||||
#SSL-END
|
||||
|
||||
#REWRITE-START
|
||||
include /www/server/panel/vhost/rewrite/html_jf.lehoo6.com.conf;
|
||||
#REWRITE-END
|
||||
|
||||
location /api/front {
|
||||
proxy_pass http://127.0.0.1:30033/api/front;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
location /api/admin {
|
||||
proxy_pass http://127.0.0.1:30032/api/admin;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
location /api/external {
|
||||
proxy_pass http://127.0.0.1:30032/api/external;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
location ^~ / {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
if (!-f $request_filename) {
|
||||
proxy_pass http://jifenmall_h5;
|
||||
}
|
||||
}
|
||||
|
||||
access_log /www/wwwlogs/jf.lehoo6.com.log;
|
||||
error_log /www/wwwlogs/jf.lehoo6.com.error.log;
|
||||
}
|
||||
59
deploy/docker/nginx/jfadmin.lehoo6.com.conf
Normal file
59
deploy/docker/nginx/jfadmin.lehoo6.com.conf
Normal file
@@ -0,0 +1,59 @@
|
||||
upstream jifenmall_admin {
|
||||
server 127.0.0.1:18081;
|
||||
keepalive 10240;
|
||||
}
|
||||
|
||||
server
|
||||
{
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
server_name jfadmin.lehoo6.com;
|
||||
index index.html index.htm default.htm default.html;
|
||||
root /www/wwwroot/jfadmin.lehoo6.com;
|
||||
include /www/server/panel/vhost/nginx/extension/jfadmin.lehoo6.com/*.conf;
|
||||
#CERT-APPLY-CHECK--START
|
||||
include /www/server/panel/vhost/nginx/well-known/jfadmin.lehoo6.com.conf;
|
||||
#CERT-APPLY-CHECK--END
|
||||
|
||||
#SSL-START
|
||||
set $isRedcert 1;
|
||||
if ($server_port != 443) {
|
||||
set $isRedcert 2;
|
||||
}
|
||||
if ( $uri ~ /\.well-known/ ) {
|
||||
set $isRedcert 1;
|
||||
}
|
||||
if ($isRedcert != 1) {
|
||||
rewrite ^(/.*)$ https://$host$1 permanent;
|
||||
}
|
||||
ssl_certificate /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.pem;
|
||||
ssl_certificate_key /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.key;
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_tickets on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
error_page 497 https://$host$request_uri;
|
||||
#SSL-END
|
||||
|
||||
#REWRITE-START
|
||||
include /www/server/panel/vhost/rewrite/html_jfadmin.lehoo6.com.conf;
|
||||
#REWRITE-END
|
||||
|
||||
location ^~ / {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
if (!-f $request_filename) {
|
||||
proxy_pass http://jifenmall_admin;
|
||||
}
|
||||
}
|
||||
|
||||
access_log /www/wwwlogs/jfadmin.lehoo6.com.log;
|
||||
error_log /www/wwwlogs/jfadmin.lehoo6.com.error.log;
|
||||
}
|
||||
55
deploy/docker/nginx/lehoo6.com.conf
Normal file
55
deploy/docker/nginx/lehoo6.com.conf
Normal file
@@ -0,0 +1,55 @@
|
||||
upstream resell_h5 {
|
||||
server 127.0.0.1:18080;
|
||||
keepalive 10240;
|
||||
}
|
||||
|
||||
server
|
||||
{
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
server_name lehoo6.com;
|
||||
index index.html index.htm default.htm default.html;
|
||||
root /www/wwwroot/lehoo6.com;
|
||||
include /www/server/panel/vhost/nginx/extension/lehoo6.com/*.conf;
|
||||
#CERT-APPLY-CHECK--START
|
||||
include /www/server/panel/vhost/nginx/well-known/lehoo6.com.conf;
|
||||
#CERT-APPLY-CHECK--END
|
||||
|
||||
#SSL-START
|
||||
set $isRedcert 1;
|
||||
if ($server_port != 443) {
|
||||
set $isRedcert 2;
|
||||
}
|
||||
if ( $uri ~ /\.well-known/ ) {
|
||||
set $isRedcert 1;
|
||||
}
|
||||
if ($isRedcert != 1) {
|
||||
rewrite ^(/.*)$ https://$host$1 permanent;
|
||||
}
|
||||
ssl_certificate /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.pem;
|
||||
ssl_certificate_key /www/wwwroot/integral-shop/deploy/docker/ssl-cert/lehoo6.com_cert/nginx/lehoo6.com.key;
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_tickets on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
error_page 497 https://$host$request_uri;
|
||||
#SSL-END
|
||||
|
||||
location ^~ / {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
if (!-f $request_filename) {
|
||||
proxy_pass http://resell_h5;
|
||||
}
|
||||
}
|
||||
|
||||
access_log /www/wwwlogs/lehoo6.com.log;
|
||||
error_log /www/wwwlogs/lehoo6.com.error.log;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# =============================================================
|
||||
# 积分商城 管理端 API(miao-admin-2.2.jar)
|
||||
# JAR 由宿主机 bind-mount 进来(/app/app.jar),无需 Maven 编译
|
||||
# 宿主机路径:${SINGLE_ADMIN_JAR} → /app/app.jar
|
||||
# 宿主机路径:${SINGLE_ADMIN_JAR} -> /app/app.jar
|
||||
# FTP 更新 JAR 后:docker compose --env-file .env restart single-admin-api
|
||||
# =============================================================
|
||||
|
||||
@@ -11,7 +11,6 @@ ENV TZ=Asia/Shanghai \
|
||||
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# 切换阿里云 Ubuntu 镜像源(服务器访问 archive.ubuntu.com 超时)
|
||||
RUN sed -i \
|
||||
-e 's|http://archive.ubuntu.com|https://mirrors.aliyun.com|g' \
|
||||
-e 's|http://security.ubuntu.com|https://mirrors.aliyun.com|g' \
|
||||
@@ -25,10 +24,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /app /config /usr/local/crmeb/crmebimage /app/log
|
||||
|
||||
# 堆大小(可通过 compose environment 覆盖)
|
||||
ENV JAVA_HEAP_OPTS="-Xms128m -Xmx256m"
|
||||
|
||||
# Spring Boot 2.2.6 + Java 17 必须的模块开放参数
|
||||
ENV JAVA_MODULE_OPTS="\
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED \
|
||||
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
|
||||
@@ -39,10 +36,8 @@ ENV JAVA_MODULE_OPTS="\
|
||||
--add-opens java.base/java.net=ALL-UNNAMED"
|
||||
|
||||
WORKDIR /app
|
||||
# /app/app.jar 由 docker-compose volumes bind-mount 进来
|
||||
EXPOSE 30032
|
||||
|
||||
# 等价于:nohup java -Xms128m -Xmx256m -jar miao-admin-2.2.jar > admin.log &
|
||||
ENTRYPOINT ["sh","-c","exec java \
|
||||
$JAVA_HEAP_OPTS \
|
||||
$JAVA_MODULE_OPTS \
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
# 积分商城 管理后台前端(Vue 2 SPA)
|
||||
# 纯 Nginx 运行时镜像,不含 Node 构建阶段
|
||||
# 静态文件由宿主机 bind-mount 进来(${SINGLE_ADMIN_WEB_DIR}:/usr/share/nginx/html)
|
||||
# 宿主机目录示例:/www/wwwroot/leilei-jfadmin.czchunfang.com/
|
||||
# 更新方式:rsync 新 dist 到宿主机目录 → 无需重建镜像
|
||||
# 更新方式:rsync 新 dist 到宿主机目录 -> 无需重建镜像
|
||||
# =============================================================
|
||||
|
||||
FROM nginx:1.25-alpine
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
# 切换阿里云 Alpine 镜像源
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk add --no-cache tzdata \
|
||||
&& cp /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||
&& echo $TZ > /etc/timezone \
|
||||
&& rm -f /etc/apk/cache/*.apk
|
||||
|
||||
# Nginx 反代配置
|
||||
# /api/ 和 /adminapi/ 代理到 single-admin-api 容器
|
||||
RUN cat > /etc/nginx/conf.d/default.conf <<'NGX'
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
@@ -8,7 +8,8 @@ server:
|
||||
port: ${SERVER_PORT:-30032}
|
||||
|
||||
crmeb:
|
||||
imagePath: /usr/local/crmeb/crmebimage/
|
||||
imagePath: /usr/local/crmeb/
|
||||
domain: ${CRMEB_DOMAIN:https://b3y45.com/}
|
||||
captchaOn: false
|
||||
asyncConfig: true
|
||||
demoSite: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# =============================================================
|
||||
# 积分商城 用户端 API(miao-front-2.2.jar)
|
||||
# JAR 由宿主机 bind-mount 进来(/app/app.jar),无需 Maven 编译
|
||||
# 宿主机路径:${SINGLE_FRONT_JAR} → /app/app.jar
|
||||
# 宿主机路径:${SINGLE_FRONT_JAR} -> /app/app.jar
|
||||
# FTP 更新 JAR 后:docker compose --env-file .env restart single-front-api
|
||||
# =============================================================
|
||||
|
||||
@@ -11,7 +11,6 @@ ENV TZ=Asia/Shanghai \
|
||||
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# 切换阿里云 Ubuntu 镜像源(服务器访问 archive.ubuntu.com 超时)
|
||||
RUN sed -i \
|
||||
-e 's|http://archive.ubuntu.com|https://mirrors.aliyun.com|g' \
|
||||
-e 's|http://security.ubuntu.com|https://mirrors.aliyun.com|g' \
|
||||
@@ -25,10 +24,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /app /config /usr/local/crmeb/crmebimage /app/log
|
||||
|
||||
# 堆大小(可通过 compose environment 覆盖)
|
||||
ENV JAVA_HEAP_OPTS="-Xms128m -Xmx256m"
|
||||
|
||||
# Spring Boot 2.2.6 + Java 17 必须的模块开放参数
|
||||
ENV JAVA_MODULE_OPTS="\
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED \
|
||||
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
|
||||
@@ -39,10 +36,8 @@ ENV JAVA_MODULE_OPTS="\
|
||||
--add-opens java.base/java.net=ALL-UNNAMED"
|
||||
|
||||
WORKDIR /app
|
||||
# /app/app.jar 由 docker-compose volumes bind-mount 进来
|
||||
EXPOSE 30033
|
||||
|
||||
# 等价于:nohup java -Xms128m -Xmx256m -jar miao-front-2.2.jar > front.log &
|
||||
ENTRYPOINT ["sh","-c","exec java \
|
||||
$JAVA_HEAP_OPTS \
|
||||
$JAVA_MODULE_OPTS \
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
# =============================================================
|
||||
# 积分商城 用户端 H5(uni-app SPA)
|
||||
# 积分商城 用户端 H5(uni-app/HBuilder SPA)
|
||||
# 纯 Nginx 运行时镜像,不含 Node 构建阶段
|
||||
# 静态文件由宿主机 bind-mount 进来(${SINGLE_H5_DIR}:/usr/share/nginx/html)
|
||||
# 宿主机目录示例:/www/wwwroot/leilei-jf.czchunfang.com/
|
||||
# 更新方式:rsync 新 dist 到宿主机目录 → 无需重建镜像
|
||||
# 更新方式:HBuilder 编译后 rsync 新 H5 产物到宿主机目录 -> 无需重建镜像
|
||||
# =============================================================
|
||||
|
||||
FROM nginx:1.25-alpine
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
# 切换阿里云 Alpine 镜像源
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk add --no-cache tzdata \
|
||||
&& cp /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||
&& echo $TZ > /etc/timezone \
|
||||
&& rm -f /etc/apk/cache/*.apk
|
||||
|
||||
# Nginx 反代配置
|
||||
# API 请求代理到 single-front-api 容器(Docker 内网,不经宝塔 Nginx)
|
||||
RUN cat > /etc/nginx/conf.d/default.conf <<'NGX'
|
||||
server {
|
||||
listen 80;
|
||||
@@ -34,7 +30,6 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# 前台 API(单点登录/商品/订单等)
|
||||
location /api/ {
|
||||
proxy_pass http://single-front-api:30033/api/;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# 仅供参考: 内容已内联到 admin-web.Dockerfile
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
client_max_body_size 50m;
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|woff2?|ttf|map)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, max-age=2592000, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://single-admin-api:30032/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
location /adminapi/ {
|
||||
proxy_pass http://single-admin-api:30032/adminapi/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
# 仅供参考: 内容已内联到 h5.Dockerfile
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
client_max_body_size 50m;
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|woff2?|ttf|map)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, max-age=2592000, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://single-front-api:30031/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# =============================================================
|
||||
# 步骤一:寄卖商城环境变量 — 池州雷蕾商贸 czleilei240
|
||||
# cp .env.example .env 并填入真实密码
|
||||
# .env 不入库
|
||||
# =============================================================
|
||||
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
# ---------- Redis(容器内) ----------
|
||||
REDIS_PASSWORD=change-me-redis
|
||||
|
||||
# ---------- H5 对外域名(浏览器可达) ----------
|
||||
INTEGRAL_TITLE=池州雷蕾商贸
|
||||
INTEGRAL_API_PUBLIC_URL=https://leileiadmin.czchunfang.com
|
||||
INTEGRAL_IMG_PUBLIC_URL=https://leileiadmin.czchunfang.com
|
||||
INTEGRAL_H5_PUBLIC_URL=https://leilei.czchunfang.com/
|
||||
INTEGRAL_SN_ID=17533260260517
|
||||
INTEGRAL_APP_STR=ZFyTNQTWEkCBczKzyUDJWE9Ecx260517
|
||||
INTEGRAL_CONTRACT_PAGE=10012
|
||||
|
||||
# ---------- 宿主机暴露端口 ----------
|
||||
INTEGRAL_H5_PORT=18080
|
||||
# webman API 直连端口(宝塔 Nginx leileiadmin.czchunfang.com → 此端口)
|
||||
RESELL_API_PORT=18085
|
||||
|
||||
# ---------- 宿主机目录映射(bind mount,与原部署路径一致)----------
|
||||
# 寄卖商城 H5 静态文件目录(手动改 JS/configs.js 直接生效,无需重建镜像)
|
||||
RESELL_H5_DIR=/www/wwwroot/leilei.czchunfang.com
|
||||
# webman 后台完整应用目录(FTP 上传新 webman.bin/public/ 后 restart 容器即可更新)
|
||||
# 上传图片、public/upload 等均包含在此目录内,无需单独挂载
|
||||
RESELL_HOUTAI_DIR=/www/wwwroot/leileiadmin.czchunfang.com
|
||||
2
deploy/docker/step1-integral/.gitignore
vendored
2
deploy/docker/step1-integral/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
.env
|
||||
houtai.env
|
||||
@@ -1,126 +0,0 @@
|
||||
# 步骤一:寄卖商城 Docker 部署(池州雷蕾商贸 czleilei240)
|
||||
|
||||
项目:`integral-resell`(寄卖商城)
|
||||
服务:`redis` · `integral-houtai`(Webman PHP 8.0)· `integral-h5`(Nginx 静态站)
|
||||
|
||||
步骤二(积分商城)与本步骤完全独立,可以单独部署、单独重启。
|
||||
|
||||
---
|
||||
|
||||
## 快速部署
|
||||
|
||||
```bash
|
||||
cd deploy/docker/step1-integral
|
||||
|
||||
# 1. 准备环境变量
|
||||
cp .env.example .env
|
||||
cp houtai.env.example houtai.env
|
||||
vim .env # 填入 REDIS_PASSWORD
|
||||
vim houtai.env # 填入 DB_PASSWORD(RDS 密码)、REDIS_PASSWORD(同 .env)
|
||||
|
||||
# 2. 首次部署:在服务器上确保宿主机目录存在
|
||||
# (若原部署目录已存在则跳过)
|
||||
mkdir -p /www/wwwroot/leilei.czchunfang.com
|
||||
mkdir -p /www/wwwroot/leileiadmin.czchunfang.com/public/upload
|
||||
|
||||
# 3. 将 H5 静态文件同步到宿主机目录(首次 / 每次前端更新后)
|
||||
rsync -av integral-resell/h5/ /www/wwwroot/leilei.czchunfang.com/
|
||||
|
||||
# 4. 构建并启动
|
||||
docker compose --env-file .env up -d --build
|
||||
|
||||
# 5. 查看状态
|
||||
docker compose --env-file .env ps
|
||||
docker compose --env-file .env logs -f integral-houtai
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 目录映射(宿主机 ↔ 容器)
|
||||
|
||||
| 宿主机路径 | 容器路径 | 用途 |
|
||||
|---|---|---|
|
||||
| `/www/wwwroot/leilei.czchunfang.com` | `/usr/share/nginx/html` | H5 静态文件,手动改 JS 即时生效 |
|
||||
| `/www/wwwroot/leileiadmin.czchunfang.com/public/upload` | `/app/public/upload` | webman 后台上传文件 |
|
||||
| `./houtai.env` | `/app/.env` | 运行时配置,只读挂入,不打进镜像 |
|
||||
| `integral-runtime`(named vol)| `/app/runtime` | webman PID、session 等运行时数据 |
|
||||
|
||||
> **H5 文件更新流程**:直接修改 `/www/wwwroot/leilei.czchunfang.com/` 下的文件(如 JS bundle、configs.js),
|
||||
> Nginx 下次请求时自动读取新文件,**无需重启容器**。
|
||||
> 仅当 Nginx 配置或镜像本身需要变更时,才需要 `docker compose build integral-h5`。
|
||||
|
||||
| 域名 | 用途 | Docker 容器端口 | 宿主机端口 | 宝塔 upstream |
|
||||
|---|---|---|---|---|
|
||||
| `leilei.czchunfang.com` | 寄卖商城 H5 | integral-h5:80 | **18080** | `resell_h5` |
|
||||
| `leileiadmin.czchunfang.com` | 寄卖商城 API / 后台 | integral-houtai:**8785** | **18085** | `resell_api` |
|
||||
|
||||
> webman.bin 写死监听 **8785** 端口。
|
||||
> - H5 容器内部 Nginx 已将 `/api/` 和 `/upload/` 代理到 `integral-houtai:8785`(Docker 内网,无需暴露)
|
||||
> - 宝塔 Nginx 的 `leileiadmin.czchunfang.com` 直连宿主机 **18085**(映射到 webman 8785)
|
||||
|
||||
---
|
||||
|
||||
## 宝塔 Nginx 配置
|
||||
|
||||
将以下两个文件内容分别粘贴到宝塔面板对应站点的「配置文件」中:
|
||||
|
||||
| 配置文件 | 说明 |
|
||||
|---|---|
|
||||
| `deploy/docker/nginx/leilei.czchunfang.com.conf` | H5 站点,upstream → 127.0.0.1:18080 |
|
||||
| `deploy/docker/nginx/leileiadmin.czchunfang.com.conf` | API 站点,upstream → 127.0.0.1:18085 |
|
||||
|
||||
证书路径(文件已在项目中):
|
||||
|
||||
```
|
||||
deploy/docker/ssl-cert/
|
||||
leilei.czchunfang.com_cert/nginx/leilei.czchunfang.com.{pem,key}
|
||||
leileiadmin.czchunfang.com_cert/nginx/leileiadmin.czchunfang.com.{pem,key}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
| 地址 | 预期 |
|
||||
|------|------|
|
||||
| `https://leilei.czchunfang.com/` | 寄卖商城 H5 首页(生产) |
|
||||
| `https://leileiadmin.czchunfang.com/api/...` | 寄卖商城 API(生产) |
|
||||
| `http://116.62.83.240:18080/` | H5 直连测试(绕过域名/SSL) |
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 重启 webman
|
||||
docker compose --env-file .env restart integral-houtai
|
||||
|
||||
# 看 webman 日志
|
||||
docker compose --env-file .env logs -f integral-houtai
|
||||
|
||||
# 进入 webman 容器
|
||||
docker compose --env-file .env exec integral-houtai bash
|
||||
|
||||
# 仅重建 H5(改了 .env 中的域名参数后)
|
||||
docker compose --env-file .env build integral-h5
|
||||
docker compose --env-file .env up -d integral-h5
|
||||
|
||||
# 停止(保留卷)
|
||||
docker compose --env-file .env down
|
||||
|
||||
# 停止并删除所有卷(慎用:清空上传图片和 runtime)
|
||||
docker compose --env-file .env down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键一致性检查
|
||||
|
||||
| 位置 | 值 |
|
||||
|---|---|
|
||||
| `.env` INTEGRAL_API_PUBLIC_URL | `https://leileiadmin.czchunfang.com` |
|
||||
| `.env` INTEGRAL_H5_PUBLIC_URL | `https://leilei.czchunfang.com/` |
|
||||
| `.env` INTEGRAL_APP_STR | `ZFyTNQTWEkCBczKzyUDJWE9Ecx260517` |
|
||||
| `houtai.env` APP_SECRET | **同上** |
|
||||
| `.env` INTEGRAL_SN_ID | `17533260260517` |
|
||||
| `h5/static/configs.js` sn_id | **同上** |
|
||||
@@ -1,95 +0,0 @@
|
||||
# =============================================================
|
||||
# 步骤一:寄卖商城(integral-resell)独立部署
|
||||
# 客户:池州雷蕾商贸 czleilei240
|
||||
# 包含服务:redis · integral-houtai(webman) · integral-h5(Nginx)
|
||||
# =============================================================
|
||||
|
||||
name: resell-czleilei240
|
||||
|
||||
x-common: &common
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "20m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
integral-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
integral-redis-data:
|
||||
integral-runtime:
|
||||
|
||||
services:
|
||||
# ---------- Redis ----------
|
||||
redis:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: redis.Dockerfile
|
||||
image: resell-czleilei240/redis:local
|
||||
container_name: integral-redis
|
||||
command: ["--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- integral-redis-data:/data
|
||||
networks: [integral-net]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
# ---------- Webman 后端 ----------
|
||||
integral-houtai:
|
||||
<<: *common
|
||||
build:
|
||||
context: ../../../integral-resell/houtai
|
||||
dockerfile: ../../deploy/docker/integral-resell/houtai.Dockerfile
|
||||
image: resell-czleilei240/houtai:latest
|
||||
container_name: integral-houtai
|
||||
networks: [integral-net]
|
||||
ports:
|
||||
# 宝塔 Nginx 直连 webman API(webman.bin 写死监听 8785)
|
||||
- "${RESELL_API_PORT:-18085}:8785"
|
||||
volumes:
|
||||
# 整个应用目录挂到宿主机 /www/wwwroot/leileiadmin.czchunfang.com/
|
||||
# FTP 上传新 webman.bin / public/ 后 docker compose restart integral-houtai 即可生效
|
||||
- ${RESELL_HOUTAI_DIR}:/app
|
||||
# .env 单独挂入(覆盖宿主机目录里的 .env),避免明文密码出现在 wwwroot
|
||||
- ./houtai.env:/app/.env:ro
|
||||
# runtime 使用命名卷(日志/session/pid 不受 FTP 覆盖影响)
|
||||
- integral-runtime:/app/runtime
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# ---------- H5 静态站 ----------
|
||||
integral-h5:
|
||||
<<: *common
|
||||
build:
|
||||
context: ../../../integral-resell/h5
|
||||
dockerfile: ../../deploy/docker/integral-resell/h5.Dockerfile
|
||||
image: resell-czleilei240/h5:latest
|
||||
container_name: integral-h5
|
||||
networks: [integral-net]
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
INTEGRAL_TITLE: ${INTEGRAL_TITLE}
|
||||
INTEGRAL_API_PUBLIC_URL: ${INTEGRAL_API_PUBLIC_URL}
|
||||
INTEGRAL_IMG_PUBLIC_URL: ${INTEGRAL_IMG_PUBLIC_URL}
|
||||
INTEGRAL_H5_PUBLIC_URL: ${INTEGRAL_H5_PUBLIC_URL}
|
||||
INTEGRAL_SN_ID: ${INTEGRAL_SN_ID}
|
||||
INTEGRAL_APP_STR: ${INTEGRAL_APP_STR}
|
||||
INTEGRAL_CONTRACT_PAGE: ${INTEGRAL_CONTRACT_PAGE}
|
||||
volumes:
|
||||
# H5 静态文件目录挂到宿主机,手动更新 JS/configs.js 直接生效,无需重建镜像
|
||||
# 子目录 crmebimage/ 同时由步骤二 Java 后端写入(PDF/图片),Nginx 直接对外提供访问
|
||||
- ${RESELL_H5_DIR}:/usr/share/nginx/html
|
||||
ports:
|
||||
- "${INTEGRAL_H5_PORT:-18080}:80"
|
||||
depends_on:
|
||||
- integral-houtai
|
||||
@@ -1,37 +0,0 @@
|
||||
# =============================================================
|
||||
# Webman 后端运行时配置 — 池州雷蕾商贸 czleilei240(寄卖商城)
|
||||
# cp houtai.env.example houtai.env 并填入真实密码
|
||||
# houtai.env 不入库,由 docker-compose volumes: 挂入 /app/.env
|
||||
# =============================================================
|
||||
|
||||
# MySQL(阿里云 RDS)
|
||||
DB_HOST = 'rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com'
|
||||
DB_PORT = 3306
|
||||
DB_DATABASE = 'yangtangyoupin'
|
||||
DB_USERNAME = 'yangtangyoupin'
|
||||
DB_PASSWORD = 'change-me'
|
||||
|
||||
# Redis(指向同 compose 内的 redis 容器)
|
||||
REDIS_HOST = 'redis'
|
||||
REDIS_PORT = 6379
|
||||
REDIS_PASSWORD = 'change-me-redis'
|
||||
|
||||
# 短信(池州雷蕾商贸专属通道)
|
||||
SMS_CHANNEL = 'alibaba'
|
||||
SMS_SIGNNAME = '池州雷蕾商贸'
|
||||
SMS_TEMPLATE = 'SMS_334320185'
|
||||
SMS_KEYID = 'LTAI5t7CfS15hZGdNLLEMUwG'
|
||||
SMS_KEYSECRET = 'ikfTvHbMMg5sStGgdvLNL8iuVYdner'
|
||||
SMS_SDKAPPID = ''
|
||||
|
||||
# OSS(不启用则走本地 public/upload)
|
||||
FILE_STORAGE = 'public'
|
||||
OSS_ACCESS_ID = ''
|
||||
OSS_ACCESS_SECRET = ''
|
||||
OSS_BUCKET = ''
|
||||
OSS_ENDPOINT = ''
|
||||
OSS_URL = ''
|
||||
|
||||
# 业务标识(须与 H5 configs.js 的 sn_id / appStr 以及积分商城 admin 后台一致)
|
||||
APP_SIGN = '1'
|
||||
APP_SECRET = 'ZFyTNQTWEkCBczKzyUDJWE9Ecx260517'
|
||||
@@ -1,13 +0,0 @@
|
||||
# 使用 Alpine 通过 apk 安装 Redis,绕过镜像源问题
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk add --no-cache redis tzdata \
|
||||
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone
|
||||
|
||||
VOLUME /data
|
||||
WORKDIR /data
|
||||
EXPOSE 6379
|
||||
|
||||
ENTRYPOINT ["redis-server"]
|
||||
@@ -1,5 +1,5 @@
|
||||
# =============================================================
|
||||
# 步骤二:积分商城环境变量 — 池州雷蕾商贸 czleilei240
|
||||
# 步骤二:积分商城环境变量 — 鼎信汇商贸 bygsf212
|
||||
# 使用方法:cp .env.example .env 然后填入真实密码
|
||||
# .env 不入库(已加入 .gitignore)
|
||||
# =============================================================
|
||||
@@ -11,16 +11,15 @@ REDIS_PASSWORD=change-me-redis
|
||||
|
||||
# ---------- 阿里云 RDS ----------
|
||||
RDS_HOST=rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
RDS_DB=yangtangyoupin
|
||||
RDS_DB=bygsf212
|
||||
RDS_USER=yangtangyoupin
|
||||
RDS_PASSWORD=change-me
|
||||
|
||||
# ---------- 订单同步(多商户 source / target) ----------
|
||||
SYNC_SOURCE_ID=shop_15
|
||||
SYNC_TARGET_MER_ID=15
|
||||
SYNC_SOURCE_ID=shop_18
|
||||
SYNC_TARGET_MER_ID=18
|
||||
|
||||
# ---------- Java JAR 宿主机路径(FTP 更新后 restart 容器即可) ----------
|
||||
# 对应宿主机原启动命令目录:/www/wwwroot/javaapi/
|
||||
SINGLE_FRONT_JAR=/www/wwwroot/javaapi/miao-front-2.2.jar
|
||||
SINGLE_ADMIN_JAR=/www/wwwroot/javaapi/miao-admin-2.2.jar
|
||||
|
||||
@@ -29,15 +28,14 @@ SINGLE_FRONT_LOG_DIR=/www/wwwroot/javaapi/logs/front
|
||||
SINGLE_ADMIN_LOG_DIR=/www/wwwroot/javaapi/logs/admin
|
||||
|
||||
# ---------- 图片/PDF 目录(与步骤一 H5 Nginx 共享宿主机路径) ----------
|
||||
# Java 后端写入 /usr/local/crmeb/crmebimage/ → 宿主机 /www/wwwroot/leilei.czchunfang.com/crmebimage/
|
||||
# 步骤一的 H5 Nginx(leilei.czchunfang.com)提供对外访问
|
||||
CRMEB_IMAGE_DIR=/www/wwwroot/leilei.czchunfang.com/crmebimage
|
||||
CRMEB_IMAGE_DIR=/www/wwwroot/b3y45.com
|
||||
CRMEB_DOMAIN=https://b3y45.com/
|
||||
|
||||
# ---------- 前端静态目录(bind-mount,rsync 更新后立即生效) ----------
|
||||
# 积分商城 H5(uni-app SPA),对应域名 leilei-jf.czchunfang.com
|
||||
SINGLE_H5_DIR=/www/wwwroot/leilei-jf.czchunfang.com
|
||||
# 积分商城管理后台(Vue SPA),对应域名 leilei-jfadmin.czchunfang.com
|
||||
SINGLE_ADMIN_WEB_DIR=/www/wwwroot/leilei-jfadmin.czchunfang.com
|
||||
# 积分商城 H5(uni-app/HBuilder 编译产物),对应域名 jf.b3y45.com
|
||||
SINGLE_H5_DIR=/www/wwwroot/jf.b3y45.com
|
||||
# 积分商城管理后台(Vue SPA),对应域名 jfadmin.b3y45.com
|
||||
SINGLE_ADMIN_WEB_DIR=/www/wwwroot/jfadmin.b3y45.com
|
||||
|
||||
# ---------- 宿主机暴露端口(供宝塔 Nginx 反代) ----------
|
||||
SINGLE_ADMIN_PORT=18081
|
||||
120
deploy/docker/step2-single-shop-bygsf212/README.md
Normal file
120
deploy/docker/step2-single-shop-bygsf212/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# 步骤二:积分商城 Docker 部署(鼎信汇商贸 bygsf212)
|
||||
|
||||
项目:`single-shop-22`(积分商城)
|
||||
服务:`redis` · `single-front-api`(Spring Boot)· `single-admin-api`(Spring Boot)
|
||||
`single-admin-web`(Vue 管理后台)· `single-h5`(uni-app/HBuilder H5)
|
||||
|
||||
步骤一(寄卖商城)与本步骤完全独立,可以单独部署、单独重启。
|
||||
|
||||
默认域名:
|
||||
- 积分商城 H5:`jf.b3y45.com`
|
||||
- 积分商城管理后台:`jfadmin.b3y45.com`
|
||||
|
||||
---
|
||||
|
||||
## 部署前提:宿主机文件准备
|
||||
|
||||
### 1. Java JAR(Spring Boot API)
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/javaapi/logs/front
|
||||
mkdir -p /www/wwwroot/javaapi/logs/admin
|
||||
|
||||
scp single-shop-22/backend/crmeb-front/target/miao-front-2.2.jar root@118.31.36.212:/www/wwwroot/javaapi/
|
||||
scp single-shop-22/backend/crmeb-admin/target/miao-admin-2.2.jar root@118.31.36.212:/www/wwwroot/javaapi/
|
||||
```
|
||||
|
||||
### 2. 前端静态文件
|
||||
|
||||
`single_uniapp22miao` 使用 HBuilder/HBuilderX 编译 H5,把编译产物上传到积分商城 H5 目录。
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/jf.b3y45.com
|
||||
rsync -a --delete single-shop-22/single_uniapp22miao/unpackage/dist/build/h5/ \
|
||||
root@118.31.36.212:/www/wwwroot/jf.b3y45.com/
|
||||
chmod -R 755 /www/wwwroot/jf.b3y45.com/
|
||||
|
||||
mkdir -p /www/wwwroot/jfadmin.b3y45.com
|
||||
rsync -a --delete single-shop-22/backend-adminend/dist/ \
|
||||
root@118.31.36.212:/www/wwwroot/jfadmin.b3y45.com/
|
||||
chmod -R 755 /www/wwwroot/jfadmin.b3y45.com/
|
||||
```
|
||||
|
||||
> 如果 HBuilderX 实际输出目录是 `unpackage/dist/build/web/`,把上面的 `build/h5/` 替换成 `build/web/`。
|
||||
|
||||
### 3. 图片/PDF 目录
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/b3y45.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快速部署
|
||||
|
||||
```bash
|
||||
cd deploy/docker/step2-single-shop-bygsf212
|
||||
|
||||
cp .env.example .env
|
||||
vim .env
|
||||
|
||||
docker compose --env-file .env build
|
||||
docker compose --env-file .env up -d
|
||||
|
||||
docker compose --env-file .env ps
|
||||
docker compose --env-file .env logs -f single-front-api
|
||||
docker compose --env-file .env logs -f single-admin-api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 域名与端口
|
||||
|
||||
| 域名 | 用途 | 宿主机端口 |
|
||||
|---|---|---|
|
||||
| `jf.b3y45.com` | 积分商城 H5(uni-app/HBuilder) | **18082** |
|
||||
| `jfadmin.b3y45.com` | 积分商城管理后台(Vue) | **18081** |
|
||||
|
||||
> Spring Boot API 端口(30032 / 30033)仅容器内监听,不对外暴露。
|
||||
> 宝塔 Nginx 通过域名反代到 `127.0.0.1:18081 / 18082`,再由容器内 Nginx 转发到 API。
|
||||
> 图片/PDF 实际落盘路径为宿主机 `/www/wwwroot/b3y45.com/crmebimage/public/...`。
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
| 地址 | 预期 |
|
||||
|------|------|
|
||||
| `https://jf.b3y45.com/` | 积分商城 H5 |
|
||||
| `https://jfadmin.b3y45.com/` | 积分商城管理后台 |
|
||||
| `http://118.31.36.212:18082/` | H5 直连测试 |
|
||||
| `http://118.31.36.212:18081/` | 管理后台直连测试 |
|
||||
|
||||
---
|
||||
|
||||
## bind-mount 目录总览
|
||||
|
||||
| 宿主机路径 | 挂入容器路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `/www/wwwroot/javaapi/miao-front-2.2.jar` | `/app/app.jar` | 用户端 API JAR |
|
||||
| `/www/wwwroot/javaapi/miao-admin-2.2.jar` | `/app/app.jar` | 管理端 API JAR |
|
||||
| `/www/wwwroot/javaapi/logs/front/` | `/app/log` | 用户端 API 日志 |
|
||||
| `/www/wwwroot/javaapi/logs/admin/` | `/app/log` | 管理端 API 日志 |
|
||||
| `/www/wwwroot/b3y45.com/` | `/usr/local/crmeb/` | 图片/PDF 写入目录 |
|
||||
| `/www/wwwroot/jf.b3y45.com/` | `/usr/share/nginx/html` | H5 静态文件 |
|
||||
| `/www/wwwroot/jfadmin.b3y45.com/` | `/usr/share/nginx/html` | 管理后台静态文件 |
|
||||
| `../single-shop/application-docker.yml` | `/config/application-docker.yml` | Spring Boot 配置 |
|
||||
|
||||
---
|
||||
|
||||
## bygsf212 关键配置对照
|
||||
|
||||
| 配置项 | 值 |
|
||||
|---|---|
|
||||
| RDS Host | `rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com` |
|
||||
| DB / User | `bygsf212` / `yangtangyoupin` |
|
||||
| imagePath 宿主机目录 | `/www/wwwroot/b3y45.com/` |
|
||||
| CRMEB_DOMAIN | `https://b3y45.com/` |
|
||||
| SYNC_SOURCE_ID | `shop_18` |
|
||||
| SYNC_TARGET_MER_ID | `18` |
|
||||
| Spring profile | `docker`(通过 `application-docker.yml` 注入) |
|
||||
164
deploy/docker/step2-single-shop-bygsf212/docker-compose.yml
Normal file
164
deploy/docker/step2-single-shop-bygsf212/docker-compose.yml
Normal file
@@ -0,0 +1,164 @@
|
||||
# =============================================================
|
||||
# 步骤二:积分商城(single-shop-22)独立部署
|
||||
# 客户:鼎信汇商贸 bygsf212
|
||||
# 包含服务:redis · single-admin-api · single-front-api
|
||||
# single-admin-web(Vue) · single-h5(uni-app/HBuilder)
|
||||
# =============================================================
|
||||
|
||||
name: jifenmall-bygsf212
|
||||
|
||||
x-common: &common
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "20m"
|
||||
max-file: "5"
|
||||
|
||||
x-spring-common: &spring-common
|
||||
<<: *common
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
MYSQL_HOST: ${RDS_HOST}
|
||||
MYSQL_DATABASE: ${RDS_DB}
|
||||
MYSQL_USERNAME: ${RDS_USER}
|
||||
MYSQL_PASSWORD: ${RDS_PASSWORD}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
CRMEB_DOMAIN: ${CRMEB_DOMAIN}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_18}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-18}
|
||||
|
||||
networks:
|
||||
single-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
single-redis-data:
|
||||
|
||||
services:
|
||||
redis:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: redis.Dockerfile
|
||||
image: jifenmall-bygsf212/redis:local
|
||||
container_name: single-redis-bygsf212
|
||||
command: ["--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- single-redis-data:/data
|
||||
networks: [single-net]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
single-front-api:
|
||||
<<: *spring-common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/front-api.Dockerfile
|
||||
image: jifenmall-bygsf212/front-api:local
|
||||
container_name: single-front-api-bygsf212
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "127.0.0.1:30033:30033"
|
||||
volumes:
|
||||
- ${SINGLE_FRONT_JAR}:/app/app.jar:ro
|
||||
- ${CRMEB_IMAGE_DIR}:/usr/local/crmeb
|
||||
- ${SINGLE_FRONT_LOG_DIR}:/app/log
|
||||
- ../single-shop/application-docker.yml:/config/application-docker.yml:ro
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
MYSQL_HOST: ${RDS_HOST}
|
||||
MYSQL_DATABASE: ${RDS_DB}
|
||||
MYSQL_USERNAME: ${RDS_USER}
|
||||
MYSQL_PASSWORD: ${RDS_PASSWORD}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
CRMEB_DOMAIN: ${CRMEB_DOMAIN}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_18}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-18}
|
||||
SERVER_PORT: 30033
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:30033/actuator/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 90s
|
||||
|
||||
single-admin-api:
|
||||
<<: *spring-common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/admin-api.Dockerfile
|
||||
image: jifenmall-bygsf212/admin-api:local
|
||||
container_name: single-admin-api-bygsf212
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "127.0.0.1:30032:30032"
|
||||
volumes:
|
||||
- ${SINGLE_ADMIN_JAR}:/app/app.jar:ro
|
||||
- ${CRMEB_IMAGE_DIR}:/usr/local/crmeb
|
||||
- ${SINGLE_ADMIN_LOG_DIR}:/app/log
|
||||
- ../single-shop/application-docker.yml:/config/application-docker.yml:ro
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
MYSQL_HOST: ${RDS_HOST}
|
||||
MYSQL_DATABASE: ${RDS_DB}
|
||||
MYSQL_USERNAME: ${RDS_USER}
|
||||
MYSQL_PASSWORD: ${RDS_PASSWORD}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
CRMEB_DOMAIN: ${CRMEB_DOMAIN}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_18}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-18}
|
||||
SERVER_PORT: 30032
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:30032/actuator/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 90s
|
||||
|
||||
single-admin-web:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/admin-web.Dockerfile
|
||||
image: jifenmall-bygsf212/admin-web:local
|
||||
container_name: single-admin-web-bygsf212
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "${SINGLE_ADMIN_PORT:-18081}:80"
|
||||
volumes:
|
||||
- ${SINGLE_ADMIN_WEB_DIR}:/usr/share/nginx/html
|
||||
depends_on:
|
||||
- single-admin-api
|
||||
|
||||
single-h5:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/h5.Dockerfile
|
||||
image: jifenmall-bygsf212/h5:local
|
||||
container_name: single-h5-bygsf212
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "${SINGLE_H5_PORT:-18082}:80"
|
||||
volumes:
|
||||
- ${SINGLE_H5_DIR}:/usr/share/nginx/html
|
||||
depends_on:
|
||||
- single-front-api
|
||||
41
deploy/docker/step2-single-shop-byhlc112/.env.example
Normal file
41
deploy/docker/step2-single-shop-byhlc112/.env.example
Normal file
@@ -0,0 +1,41 @@
|
||||
# =============================================================
|
||||
# 步骤二:积分商城环境变量 — 宝应宏煜春商贸 byhlc112
|
||||
# 使用方法:cp .env.example .env 然后填入真实密码
|
||||
# .env 不入库(已加入 .gitignore)
|
||||
# =============================================================
|
||||
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
# ---------- Redis(容器内,与步骤一独立) ----------
|
||||
REDIS_PASSWORD=change-me-redis
|
||||
|
||||
# ---------- 阿里云 RDS ----------
|
||||
RDS_HOST=rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
RDS_DB=byhlc112
|
||||
RDS_USER=yangtangyoupin
|
||||
RDS_PASSWORD=change-me
|
||||
|
||||
# ---------- 订单同步(多商户 source / target) ----------
|
||||
SYNC_SOURCE_ID=shop_16
|
||||
SYNC_TARGET_MER_ID=16
|
||||
|
||||
# ---------- Java JAR 宿主机路径(FTP 更新后 restart 容器即可) ----------
|
||||
SINGLE_FRONT_JAR=/www/wwwroot/javaapi/miao-front-2.2.jar
|
||||
SINGLE_ADMIN_JAR=/www/wwwroot/javaapi/miao-admin-2.2.jar
|
||||
|
||||
# ---------- Java 日志目录(bind-mount 到宿主机,直接 tail -f 查看) ----------
|
||||
SINGLE_FRONT_LOG_DIR=/www/wwwroot/javaapi/logs/front
|
||||
SINGLE_ADMIN_LOG_DIR=/www/wwwroot/javaapi/logs/admin
|
||||
|
||||
# ---------- 图片/PDF 目录(与步骤一 H5 Nginx 共享宿主机路径) ----------
|
||||
CRMEB_IMAGE_DIR=/www/wwwroot/h5y2c.com
|
||||
|
||||
# ---------- 前端静态目录(bind-mount,rsync 更新后立即生效) ----------
|
||||
# 积分商城 H5(uni-app SPA),对应域名 jf.h5y2c.com
|
||||
SINGLE_H5_DIR=/www/wwwroot/jf.h5y2c.com
|
||||
# 积分商城管理后台(Vue SPA),对应域名 jfadmin.h5y2c.com
|
||||
SINGLE_ADMIN_WEB_DIR=/www/wwwroot/jfadmin.h5y2c.com
|
||||
|
||||
# ---------- 宿主机暴露端口(供宝塔 Nginx 反代) ----------
|
||||
SINGLE_ADMIN_PORT=18081
|
||||
SINGLE_H5_PORT=18082
|
||||
1
deploy/docker/step2-single-shop-byhlc112/.gitignore
vendored
Normal file
1
deploy/docker/step2-single-shop-byhlc112/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.env
|
||||
123
deploy/docker/step2-single-shop-byhlc112/README.md
Normal file
123
deploy/docker/step2-single-shop-byhlc112/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 步骤二:积分商城 Docker 部署(宝应宏煜春商贸 byhlc112)
|
||||
|
||||
项目:`single-shop-22`(积分商城)
|
||||
服务:`redis` · `single-front-api`(Spring Boot)· `single-admin-api`(Spring Boot)
|
||||
`single-admin-web`(Vue 管理后台)· `single-h5`(uni-app H5)
|
||||
|
||||
步骤一(寄卖商城)与本步骤完全独立,可以单独部署、单独重启。
|
||||
|
||||
> 这套方案参考 `deploy/docker/step2-single-shop`,按 `czleilei240` 已验证结构复制。
|
||||
> 当前默认域名假设为:
|
||||
> - 积分商城 H5:`jf.h5y2c.com`
|
||||
> - 积分商城管理后台:`jfadmin.h5y2c.com`
|
||||
|
||||
---
|
||||
|
||||
## 部署前提:宿主机文件准备
|
||||
|
||||
### 1. Java JAR(Spring Boot API)
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/javaapi/logs/front
|
||||
mkdir -p /www/wwwroot/javaapi/logs/admin
|
||||
|
||||
scp single-shop-22/backend/crmeb-front/target/miao-front-2.2.jar root@39.97.236.112:/www/wwwroot/javaapi/
|
||||
scp single-shop-22/backend/crmeb-admin/target/miao-admin-2.2.jar root@39.97.236.112:/www/wwwroot/javaapi/
|
||||
```
|
||||
|
||||
### 2. 前端静态文件
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/jf.h5y2c.com
|
||||
rsync -a --delete single-shop-22/single_uniapp22miao/unpackage/dist/build/web/ \
|
||||
root@39.97.236.112:/www/wwwroot/jf.h5y2c.com/
|
||||
chmod -R 755 /www/wwwroot/jf.h5y2c.com/
|
||||
|
||||
mkdir -p /www/wwwroot/jfadmin.h5y2c.com
|
||||
rsync -a --delete single-shop-22/backend-adminend/dist/ \
|
||||
root@39.97.236.112:/www/wwwroot/jfadmin.h5y2c.com/
|
||||
chmod -R 755 /www/wwwroot/jfadmin.h5y2c.com/
|
||||
```
|
||||
|
||||
### 3. 图片/PDF 目录
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/h5y2c.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快速部署
|
||||
|
||||
```bash
|
||||
cd deploy/docker/step2-single-shop-byhlc112
|
||||
|
||||
cp .env.example .env
|
||||
vim .env
|
||||
|
||||
docker compose --env-file .env build
|
||||
docker compose --env-file .env up -d
|
||||
|
||||
docker compose --env-file .env ps
|
||||
docker compose --env-file .env logs -f single-front-api
|
||||
docker compose --env-file .env logs -f single-admin-api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 域名与端口
|
||||
|
||||
| 域名 | 用途 | 宿主机端口 |
|
||||
|---|---|---|
|
||||
| `jf.h5y2c.com` | 积分商城 H5(uni-app) | **18082** |
|
||||
| `jfadmin.h5y2c.com` | 积分商城管理后台(Vue) | **18081** |
|
||||
|
||||
> Spring Boot API 端口(30032 / 30033)仅容器内监听,不对外暴露。
|
||||
> 宝塔 Nginx 通过域名反代到 `127.0.0.1:18081 / 18082`,再由容器内 Nginx 转发到 API。
|
||||
> 图片/PDF 实际落盘路径为宿主机 `/www/wwwroot/h5y2c.com/crmebimage/public/...`。
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
| 地址 | 预期 |
|
||||
|------|------|
|
||||
| `https://jf.h5y2c.com/` | 积分商城 H5 |
|
||||
| `https://jfadmin.h5y2c.com/` | 积分商城管理后台 |
|
||||
| `http://39.97.236.112:18082/` | H5 直连测试 |
|
||||
| `http://39.97.236.112:18081/` | 管理后台直连测试 |
|
||||
|
||||
---
|
||||
|
||||
## bind-mount 目录总览
|
||||
|
||||
| 宿主机路径 | 挂入容器路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `/www/wwwroot/javaapi/miao-front-2.2.jar` | `/app/app.jar` | 用户端 API JAR |
|
||||
| `/www/wwwroot/javaapi/miao-admin-2.2.jar` | `/app/app.jar` | 管理端 API JAR |
|
||||
| `/www/wwwroot/javaapi/logs/front/` | `/app/log` | 用户端 API 日志 |
|
||||
| `/www/wwwroot/javaapi/logs/admin/` | `/app/log` | 管理端 API 日志 |
|
||||
| `/www/wwwroot/h5y2c.com/` | `/usr/local/crmeb/` | 图片/PDF 写入目录 |
|
||||
| `/www/wwwroot/jf.h5y2c.com/` | `/usr/share/nginx/html` | H5 静态文件 |
|
||||
| `/www/wwwroot/jfadmin.h5y2c.com/` | `/usr/share/nginx/html` | 管理后台静态文件 |
|
||||
| `../single-shop/application-docker.yml` | `/config/application-docker.yml` | Spring Boot 配置 |
|
||||
|
||||
---
|
||||
|
||||
## byhlc112 关键配置对照
|
||||
|
||||
| 配置项 | 值 |
|
||||
|---|---|
|
||||
| RDS Host | `rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com` |
|
||||
| DB / User | `byhlc112` / `yangtangyoupin` |
|
||||
| imagePath 宿主机目录 | `/www/wwwroot/h5y2c.com/` |
|
||||
| SYNC_SOURCE_ID | `shop_16` |
|
||||
| SYNC_TARGET_MER_ID | `16` |
|
||||
| Spring profile | `docker`(通过 `application-docker.yml` 注入) |
|
||||
|
||||
---
|
||||
|
||||
## 待确认项
|
||||
|
||||
- 如果积分管理后台域名不是 `jfadmin.h5y2c.com`,需要同步替换 `.env.example`、README 和宝塔 Nginx 配置
|
||||
- Redis 仍按 Docker 内置实例方案生成;若你想接外部 Redis,可以再帮你补一版外部 Redis 配置
|
||||
@@ -1,17 +1,11 @@
|
||||
# =============================================================
|
||||
# 步骤二:积分商城(single-shop-22)独立部署
|
||||
# 客户:池州雷蕾商贸 czleilei240
|
||||
# 客户:宝应宏煜春商贸 byhlc112
|
||||
# 包含服务:redis · single-admin-api · single-front-api
|
||||
# single-admin-web(Vue) · single-h5(uni-app)
|
||||
#
|
||||
# 优化要点(参考 step1 寄卖商城经验):
|
||||
# 1. Redis:本地 Alpine+apk 构建,不从 DockerHub 拉取镜像
|
||||
# 2. Java API:不含 Maven 编译,JAR bind-mount 自宿主机(快速部署/更新)
|
||||
# 3. 前端:Nginx only 镜像,静态文件 bind-mount(更新无需重建镜像)
|
||||
# 4. 日志:bind-mount 到宿主机,无需进容器查看
|
||||
# =============================================================
|
||||
|
||||
name: jifenmall-czleilei240
|
||||
name: jifenmall-byhlc112
|
||||
|
||||
x-common: &common
|
||||
restart: unless-stopped
|
||||
@@ -34,8 +28,8 @@ x-spring-common: &spring-common
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_15}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-15}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_16}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-16}
|
||||
|
||||
networks:
|
||||
single-net:
|
||||
@@ -45,14 +39,13 @@ volumes:
|
||||
single-redis-data:
|
||||
|
||||
services:
|
||||
# ---------- Redis(Alpine 本地构建,无需拉取 Docker Hub 镜像) ----------
|
||||
redis:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: redis.Dockerfile
|
||||
image: jifenmall-czleilei240/redis:local
|
||||
container_name: single-redis
|
||||
image: jifenmall-byhlc112/redis:local
|
||||
container_name: single-redis-byhlc112
|
||||
command: ["--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- single-redis-data:/data
|
||||
@@ -63,27 +56,20 @@ services:
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
# ---------- Front API(用户端 Spring Boot) ----------
|
||||
# JAR 文件 bind-mount 自 ${SINGLE_FRONT_JAR}(宿主机 /www/wwwroot/javaapi/miao-front-2.2.jar)
|
||||
# 更新 JAR:FTP 替换宿主机文件 → docker compose restart single-front-api
|
||||
single-front-api:
|
||||
<<: *spring-common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/front-api.Dockerfile
|
||||
image: jifenmall-czleilei240/front-api:local
|
||||
container_name: single-front-api
|
||||
image: jifenmall-byhlc112/front-api:local
|
||||
container_name: single-front-api-byhlc112
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "127.0.0.1:30033:30033"
|
||||
volumes:
|
||||
# JAR bind-mount:FTP 更新 JAR 后 restart 容器即可
|
||||
- ${SINGLE_FRONT_JAR}:/app/app.jar:ro
|
||||
# 图片/PDF 目录:与 step1 H5 Nginx 共享宿主机路径
|
||||
- ${CRMEB_IMAGE_DIR}:/usr/local/crmeb/crmebimage
|
||||
# 日志:bind-mount 到宿主机,便于直接查看
|
||||
- ${CRMEB_IMAGE_DIR}:/usr/local/crmeb
|
||||
- ${SINGLE_FRONT_LOG_DIR}:/app/log
|
||||
# Spring 配置:只读挂入
|
||||
- ../single-shop/application-docker.yml:/config/application-docker.yml:ro
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
@@ -94,8 +80,8 @@ services:
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_15}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-15}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_16}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-16}
|
||||
SERVER_PORT: 30033
|
||||
depends_on:
|
||||
redis:
|
||||
@@ -107,27 +93,20 @@ services:
|
||||
retries: 5
|
||||
start_period: 90s
|
||||
|
||||
# ---------- Admin API(管理端 Spring Boot) ----------
|
||||
# JAR 文件 bind-mount 自 ${SINGLE_ADMIN_JAR}(宿主机 /www/wwwroot/javaapi/miao-admin-2.2.jar)
|
||||
# 更新 JAR:FTP 替换宿主机文件 → docker compose restart single-admin-api
|
||||
single-admin-api:
|
||||
<<: *spring-common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/admin-api.Dockerfile
|
||||
image: jifenmall-czleilei240/admin-api:local
|
||||
container_name: single-admin-api
|
||||
image: jifenmall-byhlc112/admin-api:local
|
||||
container_name: single-admin-api-byhlc112
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "127.0.0.1:30032:30032"
|
||||
volumes:
|
||||
# JAR bind-mount
|
||||
- ${SINGLE_ADMIN_JAR}:/app/app.jar:ro
|
||||
# 图片/PDF 目录
|
||||
- ${CRMEB_IMAGE_DIR}:/usr/local/crmeb/crmebimage
|
||||
# 日志 bind-mount
|
||||
- ${CRMEB_IMAGE_DIR}:/usr/local/crmeb
|
||||
- ${SINGLE_ADMIN_LOG_DIR}:/app/log
|
||||
# Spring 配置
|
||||
- ../single-shop/application-docker.yml:/config/application-docker.yml:ro
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
@@ -138,8 +117,8 @@ services:
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_15}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-15}
|
||||
SYNC_SOURCE_ID: ${SYNC_SOURCE_ID:-shop_16}
|
||||
SYNC_TARGET_MER_ID: ${SYNC_TARGET_MER_ID:-16}
|
||||
SERVER_PORT: 30032
|
||||
depends_on:
|
||||
redis:
|
||||
@@ -151,40 +130,32 @@ services:
|
||||
retries: 5
|
||||
start_period: 90s
|
||||
|
||||
# ---------- Admin Web(Vue 管理后台,Nginx only) ----------
|
||||
# 静态文件 bind-mount 自 ${SINGLE_ADMIN_WEB_DIR}(宿主机 /www/wwwroot/leilei-jfadmin.czchunfang.com/)
|
||||
# 更新前端:rsync 新 dist/ 到宿主机目录 → 浏览器硬刷新即可(无需重启容器)
|
||||
single-admin-web:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/admin-web.Dockerfile
|
||||
image: jifenmall-czleilei240/admin-web:local
|
||||
container_name: single-admin-web
|
||||
image: jifenmall-byhlc112/admin-web:local
|
||||
container_name: single-admin-web-byhlc112
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "${SINGLE_ADMIN_PORT:-18081}:80"
|
||||
volumes:
|
||||
# 静态文件 bind-mount:rsync 更新宿主机目录后立即生效
|
||||
- ${SINGLE_ADMIN_WEB_DIR}:/usr/share/nginx/html
|
||||
depends_on:
|
||||
- single-admin-api
|
||||
|
||||
# ---------- H5 前端(uni-app SPA,Nginx only) ----------
|
||||
# 静态文件 bind-mount 自 ${SINGLE_H5_DIR}(宿主机 /www/wwwroot/leilei-jf.czchunfang.com/)
|
||||
# 更新前端:rsync 新 unpackage/dist/build/h5/ 到宿主机目录 → 无需重启容器
|
||||
single-h5:
|
||||
<<: *common
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ../single-shop/h5.Dockerfile
|
||||
image: jifenmall-czleilei240/h5:local
|
||||
container_name: single-h5
|
||||
image: jifenmall-byhlc112/h5:local
|
||||
container_name: single-h5-byhlc112
|
||||
networks: [single-net]
|
||||
ports:
|
||||
- "${SINGLE_H5_PORT:-18082}:80"
|
||||
volumes:
|
||||
# 静态文件 bind-mount
|
||||
- ${SINGLE_H5_DIR}:/usr/share/nginx/html
|
||||
depends_on:
|
||||
- single-front-api
|
||||
18
deploy/docker/step2-single-shop-byhlc112/redis.Dockerfile
Normal file
18
deploy/docker/step2-single-shop-byhlc112/redis.Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# =============================================================
|
||||
# Redis(Alpine + apk 安装,绕过 Docker Hub 镜像拉取问题)
|
||||
# 与 step1 方案一致:不依赖 docker.io,只需 registry-1.docker.io 拉 alpine:3.19
|
||||
# =============================================================
|
||||
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk add --no-cache redis tzdata \
|
||||
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||
&& rm -f /etc/apk/cache/*.apk
|
||||
|
||||
VOLUME /data
|
||||
WORKDIR /data
|
||||
EXPOSE 6379
|
||||
|
||||
ENTRYPOINT ["redis-server"]
|
||||
@@ -1,176 +0,0 @@
|
||||
# 步骤二:积分商城 Docker 部署(池州雷蕾商贸 czleilei240)
|
||||
|
||||
项目:`single-shop-22`(积分商城)
|
||||
服务:`redis` · `single-front-api`(Spring Boot)· `single-admin-api`(Spring Boot)
|
||||
`single-admin-web`(Vue 管理后台)· `single-h5`(uni-app H5)
|
||||
|
||||
步骤一(寄卖商城)与本步骤完全独立,可以单独部署、单独重启。
|
||||
|
||||
---
|
||||
|
||||
## 部署前提:宿主机文件准备
|
||||
|
||||
> JAR 和静态文件通过 **bind-mount** 挂入容器,部署前需先把文件放到宿主机对应目录。
|
||||
|
||||
### 1. Java JAR(Spring Boot API)
|
||||
|
||||
```bash
|
||||
# 宿主机目录
|
||||
mkdir -p /www/wwwroot/javaapi/logs/front
|
||||
mkdir -p /www/wwwroot/javaapi/logs/admin
|
||||
|
||||
# 将本地编译好的 JAR 传到服务器(macOS 本地执行)
|
||||
scp single-shop-22/backend/crmeb-front/target/miao-front-2.2.jar root@116.62.83.240:/www/wwwroot/javaapi/
|
||||
scp single-shop-22/backend/crmeb-admin/target/miao-admin-2.2.jar root@116.62.83.240:/www/wwwroot/javaapi/
|
||||
```
|
||||
|
||||
> 更新 JAR:FTP 替换宿主机文件 → `docker compose --env-file .env restart single-front-api`
|
||||
|
||||
### 2. 前端静态文件
|
||||
|
||||
```bash
|
||||
# H5(uni-app)
|
||||
mkdir -p /www/wwwroot/leilei-jf.czchunfang.com
|
||||
rsync -a --delete single-shop-22/single_uniapp22miao/unpackage/dist/build/h5/ \
|
||||
root@116.62.83.240:/www/wwwroot/leilei-jf.czchunfang.com/
|
||||
chmod -R 755 /www/wwwroot/leilei-jf.czchunfang.com/
|
||||
|
||||
# 管理后台(Vue)
|
||||
mkdir -p /www/wwwroot/leilei-jfadmin.czchunfang.com
|
||||
rsync -a --delete single-shop-22/backend-adminend/dist/ \
|
||||
root@116.62.83.240:/www/wwwroot/leilei-jfadmin.czchunfang.com/
|
||||
chmod -R 755 /www/wwwroot/leilei-jfadmin.czchunfang.com/
|
||||
```
|
||||
|
||||
> 更新前端:rsync 同步到宿主机 → 浏览器强刷即可,无需重建镜像或重启容器
|
||||
|
||||
### 3. 图片/PDF 目录
|
||||
|
||||
```bash
|
||||
# 与步骤一 H5 Nginx 共享,步骤一已创建则无需重建
|
||||
mkdir -p /www/wwwroot/leilei.czchunfang.com/crmebimage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快速部署
|
||||
|
||||
```bash
|
||||
cd deploy/docker/step2-single-shop
|
||||
|
||||
# 1. 准备环境变量
|
||||
cp .env.example .env
|
||||
vim .env # 填入 RDS_PASSWORD、REDIS_PASSWORD
|
||||
|
||||
# 2. 构建镜像(仅 JRE + Nginx,无 Maven/Node,约 2-5 分钟)
|
||||
docker compose --env-file .env build
|
||||
|
||||
# 3. 启动所有服务
|
||||
docker compose --env-file .env up -d
|
||||
|
||||
# 4. 查看状态
|
||||
docker compose --env-file .env ps
|
||||
docker compose --env-file .env logs -f single-front-api
|
||||
docker compose --env-file .env logs -f single-admin-api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 域名与端口
|
||||
|
||||
| 域名 | 用途 | 宿主机端口 |
|
||||
|---|---|---|
|
||||
| `leilei-jf.czchunfang.com` | 积分商城 H5(uni-app) | **18082** |
|
||||
| `leilei-jfadmin.czchunfang.com` | 积分商城管理后台(Vue) | **18081** |
|
||||
|
||||
> Spring Boot API 端口(30032 / 30033)仅容器内监听,不对外暴露。
|
||||
> 宝塔 Nginx 通过域名反代到 `127.0.0.1:18081 / 18082`,再由容器内 Nginx 转发到 API。
|
||||
|
||||
---
|
||||
|
||||
## 宝塔 Nginx 配置
|
||||
|
||||
将以下两个文件内容分别粘贴到宝塔面板对应站点的「配置文件」中:
|
||||
|
||||
| 配置文件 | 说明 |
|
||||
|---|---|
|
||||
| `deploy/docker/nginx/leilei-jf.czchunfang.com.conf` | H5 站点,upstream → 127.0.0.1:18082 |
|
||||
| `deploy/docker/nginx/leilei-jfadmin.czchunfang.com.conf` | 管理后台,upstream → 127.0.0.1:18081 |
|
||||
|
||||
证书路径(文件已在项目中):
|
||||
|
||||
```
|
||||
deploy/docker/ssl-cert/
|
||||
leilei-jf.czchunfang.com_cert/nginx/leilei-jf.czchunfang.com.{pem,key}
|
||||
leilei-jfadmin.czchunfang.com_cert/nginx/leilei-jfadmin.czchunfang.com.{pem,key}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
| 地址 | 预期 |
|
||||
|------|------|
|
||||
| `https://leilei-jf.czchunfang.com/` | 积分商城 H5(生产) |
|
||||
| `https://leilei-jfadmin.czchunfang.com/` | 积分商城管理后台(生产) |
|
||||
| `http://116.62.83.240:18082/` | H5 直连测试(绕过域名/SSL) |
|
||||
| `http://116.62.83.240:18081/` | 管理后台直连测试 |
|
||||
|
||||
---
|
||||
|
||||
## 常用运维命令
|
||||
|
||||
```bash
|
||||
# 重启 Java API(更新 JAR 后)
|
||||
docker compose --env-file .env restart single-front-api
|
||||
docker compose --env-file .env restart single-admin-api
|
||||
|
||||
# 实时日志(宿主机路径 /www/wwwroot/javaapi/logs/ 也可直接查看)
|
||||
docker compose --env-file .env logs -f single-admin-api
|
||||
docker compose --env-file .env logs -f single-front-api
|
||||
|
||||
# 进入容器
|
||||
docker compose --env-file .env exec single-admin-api bash
|
||||
|
||||
# 停止(保留卷)
|
||||
docker compose --env-file .env down
|
||||
|
||||
# 停止并删除 Redis 数据卷(慎用)
|
||||
docker compose --env-file .env down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## bind-mount 目录总览
|
||||
|
||||
| 宿主机路径 | 挂入容器路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `/www/wwwroot/javaapi/miao-front-2.2.jar` | `/app/app.jar` (front-api) | 用户端 API JAR(只读) |
|
||||
| `/www/wwwroot/javaapi/miao-admin-2.2.jar` | `/app/app.jar` (admin-api) | 管理端 API JAR(只读) |
|
||||
| `/www/wwwroot/javaapi/logs/front/` | `/app/log` (front-api) | 用户端 API 日志 |
|
||||
| `/www/wwwroot/javaapi/logs/admin/` | `/app/log` (admin-api) | 管理端 API 日志 |
|
||||
| `/www/wwwroot/leilei.czchunfang.com/crmebimage/` | `/usr/local/crmeb/crmebimage/` (两个 API) | 图片/PDF 写入目录 |
|
||||
| `/www/wwwroot/leilei-jf.czchunfang.com/` | `/usr/share/nginx/html` (h5) | H5 静态文件 |
|
||||
| `/www/wwwroot/leilei-jfadmin.czchunfang.com/` | `/usr/share/nginx/html` (admin-web) | 管理后台静态文件 |
|
||||
| `../single-shop/application-docker.yml` | `/config/application-docker.yml` (两个 API) | Spring Boot 配置(只读) |
|
||||
|
||||
---
|
||||
|
||||
## czleilei240 关键配置对照
|
||||
|
||||
| 配置项 | 值 |
|
||||
|---|---|
|
||||
| RDS Host | `rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com` |
|
||||
| DB / User | `yangtangyoupin` |
|
||||
| crmeb.imagePath(容器内) | `/usr/local/crmeb/crmebimage/` |
|
||||
| SYNC_SOURCE_ID | `shop_15` |
|
||||
| SYNC_TARGET_MER_ID | `15` |
|
||||
| Spring profile | `docker`(application-docker.yml 通过 env 注入) |
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
- JVM 参数已包含 Java 17 + Spring Boot 2.2.6 所需的 `--add-opens` 标志(见 Dockerfile)。
|
||||
- 图片/PDF 目录 `/www/wwwroot/leilei.czchunfang.com/crmebimage/` 同时挂入 `front-api` 和 `admin-api` 两个容器,确保文件共享。
|
||||
- Redis 实例(`single-redis`)与步骤一(`integral-redis`)完全独立,数据互不干扰。
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
549
docs/com-bosenyuan-miao80-data-cleanup-260604.md
Normal file
549
docs/com-bosenyuan-miao80-data-cleanup-260604.md
Normal file
@@ -0,0 +1,549 @@
|
||||
# 宝应博森元 miao80 用户数据清理方案
|
||||
|
||||
## 基本信息
|
||||
|
||||
- 公司:宝应博森元
|
||||
- Spring profile:`miao80`
|
||||
- MySQL:`123.56.214.80:3306`
|
||||
- 数据库:`yangtangyoupin`
|
||||
- 任务日期:2026-06-04
|
||||
- 方案目的:删除指定会员及其寄卖、订单、奖金、积分、地址、提现等关联数据。
|
||||
|
||||
## 数据来源
|
||||
|
||||
1. Excel 清除名单:`/Users/mac/Works26/miao-july/bosenyuan清除名单260604.xlsx`
|
||||
2. 手工补充名单:此前核查出的 30 个有隐藏未售寄卖商品的会员。
|
||||
|
||||
## 清理范围
|
||||
|
||||
### Excel 清除名单
|
||||
|
||||
| 用户ID | 昵称 | 联系方式 | 上级ID |
|
||||
| --- | --- | --- | --- |
|
||||
| 93320 | 崔龙云 | 18921916448 | 93315 |
|
||||
| 93317 | 杨长霞 | 13852763959 | 93212 |
|
||||
| 93312 | 成成 | 13905238633 | 93296 |
|
||||
| 93311 | 袁月 | 18932311595 | 93303 |
|
||||
| 93310 | 范明中 | 18915115858 | 93303 |
|
||||
| 93308 | 138****0239 | 13813100239 | 93248 |
|
||||
| 93304 | 卢红 | 13905235316 | 93303 |
|
||||
| 93302 | 周顺芹 | 13511707356 | 93250 |
|
||||
| 93298 | 顾善道 | 15005253578 | 93120 |
|
||||
| 93293 | 朱梅芳 | 13801441506 | 93136 |
|
||||
| 93292 | 蔡宛序 | 17625471367 | 93213 |
|
||||
| 93291 | 130****3522 | 13092023522 | 93254 |
|
||||
| 93288 | 于桂平 | 13305239833 | 93249 |
|
||||
| 93286 | 陆哓明 | 13952753228 | 93250 |
|
||||
| 93285 | 仲妮妮 | 17351389708 | 93212 |
|
||||
| 93282 | 张伏顺 | 13016592366 | 93254 |
|
||||
| 93281 | 189****9338 | 18951269338 | 93213 |
|
||||
| 93280 | 韩国林 | 15861304478 | 93248 |
|
||||
| 93278 | 陈文青 | 15062891641 | 92775 |
|
||||
| 93277 | 许良会 | 18260691419 | 93239 |
|
||||
| 93275 | 黄丽辉 | 15262265025 | 93147 |
|
||||
| 93269 | 吴发 | 18360337533 | 92687 |
|
||||
| 93268 | 马猛宏 | 13921912348 | 93250 |
|
||||
| 93267 | 卢玉凤 | 17558780051 | 93212 |
|
||||
| 93266 | 周同 | 13115254379 | 93265 |
|
||||
| 93265 | 王彬 | 18915131699 | 93254 |
|
||||
| 93263 | 陈斌 | 18012324636 | 93019 |
|
||||
| 93262 | 徐猫猫 | 13390629258 | 93161 |
|
||||
| 93261 | 潘年庆 | 13773341597 | 93251 |
|
||||
| 93260 | 纪开东 | 13179766726 | 93251 |
|
||||
| 93256 | 卢玉芬 | 18012323088 | 93161 |
|
||||
| 93253 | 刘加良 | 15298672227 | 93239 |
|
||||
| 93252 | 夏宝华 | 15262250561 | 93214 |
|
||||
| 93241 | 孙万万 | 13951441675 | 93214 |
|
||||
| 93238 | 戴向英 | 15189893800 | 93136 |
|
||||
| 93233 | 严登文 | 15995116338 | 93214 |
|
||||
| 93228 | 苗永粉 | 13773321176 | 92688 |
|
||||
| 93217 | 邹华 | 18796681777 | 93206 |
|
||||
| 93200 | 戴增中 | 13813104768 | 93193 |
|
||||
| 93194 | 姚焕桂 | 13773330344 | 93075 |
|
||||
| 93190 | 姜红爱 | 17715862910 | 93075 |
|
||||
| 93160 | 季安红 | 13913432863 | 93107 |
|
||||
| 93153 | 王祥 | 15380316138 | 92973 |
|
||||
| 93143 | 郑娟 | 17751330452 | 93075 |
|
||||
| 93142 | 李德荣 | 13813104919 | 93109 |
|
||||
|
||||
### 手工补充名单
|
||||
|
||||
| 用户ID | 昵称 | 联系方式 | 待清理未售商品数 |
|
||||
| --- | --- | --- | --- |
|
||||
| 92801 | 成宏梅 | 18751483086 | 3 |
|
||||
| 93011 | 李迎春 | 13505270568 | 3 |
|
||||
| 93032 | 沈宝军 | 13348149448 | 2 |
|
||||
| 93073 | 陈海霞 | 15152714200 | 1 |
|
||||
| 93078 | 顾晓燕 | 13151600166 | 1 |
|
||||
| 93120 | 于秀梅 | 13348140510 | 1 |
|
||||
| 93136 | 戴玉山 | 13813100018 | 1 |
|
||||
| 93147 | 陈小燕 | 18066016798 | 1 |
|
||||
| 93155 | 王锐 | 18952581561 | 1 |
|
||||
| 93185 | 王学梅 | 15150886020 | 1 |
|
||||
| 93193 | 相荣 | 18796692299 | 1 |
|
||||
| 93216 | 张萍 | 18252750442 | 1 |
|
||||
| 93218 | 李润芝 | 15050708588 | 1 |
|
||||
| 93248 | 董鲜 | 19533096227 | 1 |
|
||||
| 93284 | 仇云 | 17317753117 | 1 |
|
||||
| 93287 | 王珏 | 19741771099 | 1 |
|
||||
| 93290 | 段玉香 | 17751370387 | 1 |
|
||||
| 93295 | 季爱玲 | 18932366911 | 1 |
|
||||
| 93297 | 蔡先生 | 13952533248 | 1 |
|
||||
| 93301 | 毛天梅 | 18012327099 | 1 |
|
||||
| 93305 | 王素琴 | 13092025465 | 1 |
|
||||
| 93306 | 殷先生 | 15262252218 | 1 |
|
||||
| 93307 | 董先生 | 15252737658 | 1 |
|
||||
| 93315 | 朱继英 | 15800764854 | 1 |
|
||||
| 93318 | 王琴 | 15150881748 | 1 |
|
||||
| 93321 | 爱之香 | 15951439818 | 1 |
|
||||
| 93322 | 邓学美 | 13505254585 | 1 |
|
||||
| 93323 | 梁鹤贵 | 15050701288 | 1 |
|
||||
| 93325 | 李杰 | 15050708139 | 1 |
|
||||
| 93327 | 刘正娟 | 15062896288 | 1 |
|
||||
|
||||
### 合并后用户ID
|
||||
|
||||
- Excel:45 人
|
||||
- 手工补充:30 人
|
||||
- 重叠:0 人
|
||||
- 合并后:75 人
|
||||
|
||||
```sql
|
||||
SET @cleanup_user_ids = '92801,93011,93032,93073,93078,93120,93136,93142,93143,93147,93153,93155,93160,93185,93190,93193,93194,93200,93216,93217,93218,93228,93233,93238,93241,93248,93252,93253,93256,93260,93261,93262,93263,93265,93266,93267,93268,93269,93275,93277,93278,93280,93281,93282,93284,93285,93286,93287,93288,93290,93291,93292,93293,93295,93297,93298,93301,93302,93304,93305,93306,93307,93308,93310,93311,93312,93315,93317,93318,93320,93321,93322,93323,93325,93327';
|
||||
```
|
||||
|
||||
实际执行建议使用临时表承载 ID:
|
||||
|
||||
```sql
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_bosenyuan_cleanup_users;
|
||||
CREATE TEMPORARY TABLE tmp_bosenyuan_cleanup_users (
|
||||
id INT PRIMARY KEY
|
||||
);
|
||||
|
||||
INSERT INTO tmp_bosenyuan_cleanup_users (id) VALUES
|
||||
(92801),(93011),(93032),(93073),(93078),(93120),(93136),(93142),(93143),(93147),
|
||||
(93153),(93155),(93160),(93185),(93190),(93193),(93194),(93200),(93216),(93217),
|
||||
(93218),(93228),(93233),(93238),(93241),(93248),(93252),(93253),(93256),(93260),
|
||||
(93261),(93262),(93263),(93265),(93266),(93267),(93268),(93269),(93275),(93277),
|
||||
(93278),(93280),(93281),(93282),(93284),(93285),(93286),(93287),(93288),(93290),
|
||||
(93291),(93292),(93293),(93295),(93297),(93298),(93301),(93302),(93304),(93305),
|
||||
(93306),(93307),(93308),(93310),(93311),(93312),(93315),(93317),(93318),(93320),
|
||||
(93321),(93322),(93323),(93325),(93327);
|
||||
```
|
||||
|
||||
## 当前库只读统计
|
||||
|
||||
统计时间:2026-06-04,库:`miao80 / yangtangyoupin`。
|
||||
|
||||
| 表 / 范围 | 命中行数 |
|
||||
| --- | ---: |
|
||||
| `wa_users` | 75 |
|
||||
| `eb_user` | 74 |
|
||||
| `wa_merchandise` | 1698 |
|
||||
| `wa_order` seller 或 buyer 命中 | 2609 |
|
||||
| `wa_selfbonus_log` | 1627 |
|
||||
| `wa_sharebonus_log` | 1545 |
|
||||
| `wa_coupon_log` | 198 |
|
||||
| `wa_withdraw` | 44 |
|
||||
| `wa_money_log` | 0 |
|
||||
| `wa_address` | 80 |
|
||||
| `wa_alipay` | 65 |
|
||||
| `wa_bank` | 1 |
|
||||
| `eb_user_integral_record` | 1666 |
|
||||
| `eb_user_address` | 73 |
|
||||
| `eb_user_bill` | 47 |
|
||||
| `eb_user_brokerage_record` | 0 |
|
||||
| `eb_user_experience_record` | 47 |
|
||||
| `eb_user_extract` | 0 |
|
||||
| `eb_user_level` | 0 |
|
||||
| `eb_user_recharge` | 0 |
|
||||
| `eb_user_sign` | 0 |
|
||||
| `eb_user_token` | 0 |
|
||||
| `eb_user_visit_record` | 12 |
|
||||
| `eb_store_cart` | 0 |
|
||||
| `eb_store_coupon_user` | 0 |
|
||||
| `eb_store_order` | 47 |
|
||||
| `eb_sms_record` | 0 |
|
||||
|
||||
## 特殊检查
|
||||
|
||||
### `wa_users` 与 `eb_user` 不一致
|
||||
|
||||
`wa_users` 命中 75 人,`eb_user` 命中 74 人。缺失的 `eb_user`:
|
||||
|
||||
| 用户ID | 昵称 | 联系方式 |
|
||||
| --- | --- | --- |
|
||||
| 93305 | 王素琴 | 13092025465 |
|
||||
|
||||
### 删除名单外仍指向待删用户的推荐关系
|
||||
|
||||
删除前需要决定是否置空为 `0`,或改挂到指定上级。
|
||||
|
||||
`wa_users.pid` 外部引用 1 条:
|
||||
|
||||
| 用户ID | 昵称 | 联系方式 | 当前上级ID |
|
||||
| --- | --- | --- | --- |
|
||||
| 93036 | 赵玉文 | 18091856709 | 92801 |
|
||||
|
||||
`eb_user.spread_uid` 外部引用 14 条:
|
||||
|
||||
| uid | account | nickname | phone | spread_uid |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 92881 | 18115115512 | 成明强 | 18115115512 | 92801 |
|
||||
| 92903 | 18036263863 | 徐丹 | 18036263863 | 92801 |
|
||||
| 92940 | 13773342930 | 朱友华 | 13773342930 | 92801 |
|
||||
| 92976 | 13773334985 | 何军健 | 13773334985 | 92801 |
|
||||
| 92983 | 15052568923 | 柴秉丹 | 15052568923 | 92801 |
|
||||
| 93009 | 13092025465 | 王素琴 | 13092025465 | 92801 |
|
||||
| 93030 | 15298470085 | 张琴 | 15298470085 | 93011 |
|
||||
| 93036 | 18091856709 | 赵玉文 | 18091856709 | 92801 |
|
||||
| 93094 | 15952533800 | 朱鹤峰 | 15952533800 | 92801 |
|
||||
| 93105 | 18626221249 | 姚春峰 | 18626221249 | 93078 |
|
||||
| 93133 | 19352900319 | 193****0319 | 19352900319 | 93011 |
|
||||
| 93138 | 18952533228 | 189****3228 | 18952533228 | 93011 |
|
||||
| 93146 | 15252502175 | 艾保兄 | 15252502175 | 93011 |
|
||||
| 93148 | 13601446282 | 许梅 | 13601446282 | 93011 |
|
||||
|
||||
建议处理方式:
|
||||
|
||||
```sql
|
||||
UPDATE wa_users
|
||||
SET pid = 0
|
||||
WHERE pid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
AND id NOT IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
UPDATE eb_user
|
||||
SET spread_uid = 0, spread_time = NULL
|
||||
WHERE spread_uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
AND uid NOT IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
```
|
||||
|
||||
如业务要求保留推荐关系,需要先确认新的承接上级 ID,再把上面 SQL 的 `0` 替换为指定 ID。
|
||||
|
||||
## 执行前校验 SQL
|
||||
|
||||
```sql
|
||||
SELECT 'wa_users', COUNT(*) FROM wa_users WHERE id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user', COUNT(*) FROM eb_user WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_merchandise', COUNT(*) FROM wa_merchandise WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_order_seller_or_buyer', COUNT(*) FROM wa_order WHERE seller_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users) OR buyer_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_selfbonus_log', COUNT(*) FROM wa_selfbonus_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_sharebonus_log', COUNT(*) FROM wa_sharebonus_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_coupon_log', COUNT(*) FROM wa_coupon_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_withdraw', COUNT(*) FROM wa_withdraw WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_address', COUNT(*) FROM wa_address WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_alipay', COUNT(*) FROM wa_alipay WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_bank', COUNT(*) FROM wa_bank WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_integral_record', COUNT(*) FROM eb_user_integral_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_address', COUNT(*) FROM eb_user_address WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_bill', COUNT(*) FROM eb_user_bill WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_experience_record', COUNT(*) FROM eb_user_experience_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_visit_record', COUNT(*) FROM eb_user_visit_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_store_order', COUNT(*) FROM eb_store_order WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
```
|
||||
|
||||
## 备份方案
|
||||
|
||||
执行删除前,先在同库创建带日期后缀的备份表。备份表名建议固定使用本次任务时间戳:`20260604`。
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_users AS
|
||||
SELECT * FROM wa_users WHERE id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_user AS
|
||||
SELECT * FROM eb_user WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_merchandise AS
|
||||
SELECT * FROM wa_merchandise WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_order AS
|
||||
SELECT * FROM wa_order
|
||||
WHERE seller_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
OR buyer_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_selfbonus_log AS
|
||||
SELECT * FROM wa_selfbonus_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_sharebonus_log AS
|
||||
SELECT * FROM wa_sharebonus_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_coupon_log AS
|
||||
SELECT * FROM wa_coupon_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_withdraw AS
|
||||
SELECT * FROM wa_withdraw WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_address AS
|
||||
SELECT * FROM wa_address WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_alipay AS
|
||||
SELECT * FROM wa_alipay WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_wa_bank AS
|
||||
SELECT * FROM wa_bank WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_user_integral_record AS
|
||||
SELECT * FROM eb_user_integral_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_user_address AS
|
||||
SELECT * FROM eb_user_address WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_user_bill AS
|
||||
SELECT * FROM eb_user_bill WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_user_experience_record AS
|
||||
SELECT * FROM eb_user_experience_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_user_visit_record AS
|
||||
SELECT * FROM eb_user_visit_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bak_20260604_bsy_eb_store_order AS
|
||||
SELECT * FROM eb_store_order WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
```
|
||||
|
||||
## 删除执行方案
|
||||
|
||||
建议用事务执行;执行前确认业务低峰,并暂停相关同步任务或后台定时任务。
|
||||
|
||||
```sql
|
||||
START TRANSACTION;
|
||||
|
||||
-- 1. 先解除删除名单外用户对待删用户的推荐引用
|
||||
UPDATE wa_users
|
||||
SET pid = 0
|
||||
WHERE pid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
AND id NOT IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
UPDATE eb_user
|
||||
SET spread_uid = 0, spread_time = NULL
|
||||
WHERE spread_uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
AND uid NOT IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
-- 2. 删除寄卖业务关联数据
|
||||
DELETE FROM wa_order
|
||||
WHERE seller_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
OR buyer_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_merchandise
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_selfbonus_log
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_sharebonus_log
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_coupon_log
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_withdraw
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_money_log
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_address
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_alipay
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_bank
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
-- 3. 删除积分商城用户关联数据
|
||||
DELETE FROM eb_user_integral_record
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_address
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_bill
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_brokerage_record
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_experience_record
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_extract
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_level
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_recharge
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_sign
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_token
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_user_visit_record
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_store_cart
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_store_coupon_user
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_store_order
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM eb_sms_record
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
-- 4. 最后删除用户主表
|
||||
DELETE FROM eb_user
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
DELETE FROM wa_users
|
||||
WHERE id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
-- 5. 事务内复核,确认结果符合预期后再提交
|
||||
SELECT 'wa_users_remaining', COUNT(*) FROM wa_users WHERE id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_remaining', COUNT(*) FROM eb_user WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_merchandise_remaining', COUNT(*) FROM wa_merchandise WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_order_remaining', COUNT(*) FROM wa_order WHERE seller_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users) OR buyer_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_selfbonus_log_remaining', COUNT(*) FROM wa_selfbonus_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_sharebonus_log_remaining', COUNT(*) FROM wa_sharebonus_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'wa_coupon_log_remaining', COUNT(*) FROM wa_coupon_log WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'eb_user_integral_record_remaining', COUNT(*) FROM eb_user_integral_record WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'external_wa_pid_refs_remaining', COUNT(*) FROM wa_users WHERE pid IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
UNION ALL SELECT 'external_eb_spread_refs_remaining', COUNT(*) FROM eb_user WHERE spread_uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
-- 确认无误后:
|
||||
COMMIT;
|
||||
|
||||
-- 如结果异常:
|
||||
-- ROLLBACK;
|
||||
```
|
||||
|
||||
## 执行后复核
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) AS wa_users_remaining
|
||||
FROM wa_users
|
||||
WHERE id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
SELECT COUNT(*) AS eb_user_remaining
|
||||
FROM eb_user
|
||||
WHERE uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
SELECT COUNT(*) AS merchandise_remaining
|
||||
FROM wa_merchandise
|
||||
WHERE user_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
SELECT COUNT(*) AS order_remaining
|
||||
FROM wa_order
|
||||
WHERE seller_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users)
|
||||
OR buyer_id IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
SELECT COUNT(*) AS wa_pid_refs_remaining
|
||||
FROM wa_users
|
||||
WHERE pid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
|
||||
SELECT COUNT(*) AS eb_spread_refs_remaining
|
||||
FROM eb_user
|
||||
WHERE spread_uid IN (SELECT id FROM tmp_bosenyuan_cleanup_users);
|
||||
```
|
||||
|
||||
## 回滚思路
|
||||
|
||||
如果已经 `COMMIT`,只能通过备份表恢复。恢复时应优先恢复用户主表,再恢复关联表:
|
||||
|
||||
1. `wa_users`
|
||||
2. `eb_user`
|
||||
3. `wa_*` 业务表
|
||||
4. `eb_user_*` / `eb_store_*` 关联表
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
INSERT INTO wa_users
|
||||
SELECT * FROM bak_20260604_bsy_wa_users;
|
||||
|
||||
INSERT INTO eb_user
|
||||
SELECT * FROM bak_20260604_bsy_eb_user;
|
||||
```
|
||||
|
||||
恢复前需要先确认目标主键是否已经被重新占用。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 本文档仅为清理方案,尚未执行删除。
|
||||
- `wa_merchandise` 中手工补充名单此前核查到的 35 条未售商品均为隐藏状态:`status = 1`、`is_show = 0`。
|
||||
- `wa_order` 删除条件必须同时覆盖 `seller_id` 和 `buyer_id`。
|
||||
- 删除主表前必须先处理名单外用户对待删用户的 `pid` / `spread_uid` 引用。
|
||||
- 执行完成后,如系统使用 Redis 缓存用户、配置或统计数据,需要按线上运维流程清理相关缓存或重启服务。
|
||||
|
||||
## 执行结果
|
||||
|
||||
- 执行时间:2026-06-04
|
||||
- 执行库:`miao80 / yangtangyoupin`
|
||||
- 清理 ID 表:`bak_20260604_bsy_cleanup_users`
|
||||
- 清理用户数:75
|
||||
- 状态:已执行并 `COMMIT`
|
||||
|
||||
### 备份表实际行数
|
||||
|
||||
| 备份表 | 行数 |
|
||||
| --- | ---: |
|
||||
| `bak_20260604_bsy_cleanup_users` | 75 |
|
||||
| `bak_20260604_bsy_wa_users` | 75 |
|
||||
| `bak_20260604_bsy_eb_user` | 74 |
|
||||
| `bak_20260604_bsy_wa_merchandise` | 1698 |
|
||||
| `bak_20260604_bsy_wa_order` | 2609 |
|
||||
| `bak_20260604_bsy_wa_selfbonus_log` | 1627 |
|
||||
| `bak_20260604_bsy_wa_sharebonus_log` | 1545 |
|
||||
| `bak_20260604_bsy_wa_coupon_log` | 198 |
|
||||
| `bak_20260604_bsy_wa_withdraw` | 44 |
|
||||
| `bak_20260604_bsy_wa_address` | 80 |
|
||||
| `bak_20260604_bsy_wa_alipay` | 65 |
|
||||
| `bak_20260604_bsy_wa_bank` | 1 |
|
||||
| `bak_20260604_bsy_wa_money_log` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_integral_record` | 1666 |
|
||||
| `bak_20260604_bsy_eb_user_address` | 73 |
|
||||
| `bak_20260604_bsy_eb_user_bill` | 47 |
|
||||
| `bak_20260604_bsy_eb_user_brokerage_record` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_experience_record` | 47 |
|
||||
| `bak_20260604_bsy_eb_user_extract` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_level` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_recharge` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_sign` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_token` | 0 |
|
||||
| `bak_20260604_bsy_eb_user_visit_record` | 12 |
|
||||
| `bak_20260604_bsy_eb_store_cart` | 0 |
|
||||
| `bak_20260604_bsy_eb_store_coupon_user` | 0 |
|
||||
| `bak_20260604_bsy_eb_store_order` | 47 |
|
||||
| `bak_20260604_bsy_eb_sms_record` | 0 |
|
||||
|
||||
### 独立复核结果
|
||||
|
||||
| 复核项 | 剩余行数 |
|
||||
| --- | ---: |
|
||||
| `wa_users` | 0 |
|
||||
| `eb_user` | 0 |
|
||||
| `wa_merchandise` | 0 |
|
||||
| `wa_order` seller 或 buyer 命中 | 0 |
|
||||
| `wa_selfbonus_log` | 0 |
|
||||
| `wa_sharebonus_log` | 0 |
|
||||
| `wa_coupon_log` | 0 |
|
||||
| `wa_withdraw` | 0 |
|
||||
| `wa_address` | 0 |
|
||||
| `wa_alipay` | 0 |
|
||||
| `wa_bank` | 0 |
|
||||
| `eb_user_integral_record` | 0 |
|
||||
| `eb_user_address` | 0 |
|
||||
| `eb_user_bill` | 0 |
|
||||
| `eb_user_experience_record` | 0 |
|
||||
| `eb_user_visit_record` | 0 |
|
||||
| `eb_store_order` | 0 |
|
||||
| 删除名单外 `wa_users.pid` 指向待删用户 | 0 |
|
||||
| 删除名单外 `eb_user.spread_uid` 指向待删用户 | 0 |
|
||||
|
||||
说明:`information_schema.TABLES.TABLE_ROWS` 对 InnoDB 表可能是估算值;本节备份行数以执行时 `COUNT(*)` 输出为准。
|
||||
153
docs/com-bygsf212-data-imgration.md
Normal file
153
docs/com-bygsf212-data-imgration.md
Normal file
@@ -0,0 +1,153 @@
|
||||
## 公司名称: 宝应桂圣富商贸/鼎信汇商贸
|
||||
|
||||
- host ip: 118.31.36.212
|
||||
|
||||
## mysql数据库配置信息
|
||||
|
||||
datasource:
|
||||
rds: rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
name: bygsf212
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
|
||||
## 数据清理任务
|
||||
|
||||
- **用户数据范围**:`wa_users.id` / `eb_user.uid` 保留名单:
|
||||
`92688, 92880, 92904, 92964, 93098, 93141, 93164, 93235, 93251, 93259, 93267, 93270, 93272, 93273, 93276, 93284, 93292, 93300`
|
||||
|
||||
来源核对:
|
||||
- 博森元团队成员信息表.xlsx / bsy-yangtangyoupin dump:`92688, 92904, 92964, 93164, 93251, 93259, 93272, 93273, 93276`
|
||||
- 金雅文团队成员信息表.xlsx / jyw-yangtangyoupin dump:`92880, 93098, 93141, 93235, 93259, 93267, 93270, 93284, 93292, 93300`
|
||||
- 备注:`93259` 在两份源 dump 中均存在,但对应不同人员;上方保留名单按 `wa_users.id` / `eb_user.uid` 去重后记录。
|
||||
|
||||
- 保留wa_users表中id在用户id数据范围的 ,删除其余用户数据
|
||||
- 保留eb_user表中uid在用户id数据范围的 ,删除其余用户数据
|
||||
|
||||
- wa_order
|
||||
清空wa_order表中数据
|
||||
|
||||
- wa_merchandise
|
||||
从源数据dump文件中提取“created_at >= 2026-06-12”并且seller_id或buyer_id在用户id数据范围的寄售商品,删除其余数据
|
||||
(当前库表字段为 `user_id` 表示卖家,实现时按 `user_id` 与日期条件过滤。)
|
||||
|
||||
- wa_selfbonus_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_sharebonus_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_coupon_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_withdraw
|
||||
清空wa_withdraw表中数据
|
||||
|
||||
- eb_store_order
|
||||
清空eb_store_order表中数据
|
||||
|
||||
- eb_user_integral_record
|
||||
只保留用户在名单内的记录;表字段为 `uid`(与 `wa_users.id` / `eb_user.uid` 对应),实现按 `uid` 过滤。
|
||||
|
||||
## 执行结果
|
||||
|
||||
- 已于 **2026-06-14** 按当前保留名单执行清理并 `COMMIT`。
|
||||
- 执行脚本:`docs/sql/run_com_bygsf212_cleanup.py`
|
||||
- 执行前备份:`docs/sql/backups/bygsf212_cleanup_before_20260614_194640.sql.gz`(已通过 `gzip -t` 校验)
|
||||
- dump 中满足 `wa_merchandise.created_at >= 2026-06-12` 且 `user_id` 在保留名单内的记录:
|
||||
- `bsy-yangtangyoupin_2026-06-14_14-25-01_mysql_data.sql`:15 行
|
||||
- `jyw-yangtangyoupin_2026-06-14_14-55-01_mysql_data.sql`:18 行
|
||||
- 当前目标库实际命中的 `wa_merchandise` 保留记录:18 行
|
||||
- 保留后行数:
|
||||
- `wa_users`:14
|
||||
- `eb_user`:14
|
||||
- `wa_order`:0
|
||||
- `wa_merchandise`:18
|
||||
- `wa_selfbonus_log`:846
|
||||
- `wa_sharebonus_log`:764
|
||||
- `wa_coupon_log`:173
|
||||
- `wa_withdraw`:0
|
||||
- `eb_store_order`:0
|
||||
- `eb_user_integral_record`:870
|
||||
- 复核:`wa_users`、`eb_user`、`wa_selfbonus_log`、`wa_sharebonus_log`、`wa_coupon_log`、`eb_user_integral_record` 均无保留名单外记录。
|
||||
- 备注:保留名单共 18 个 ID,当前目标库仅存在其中 14 个;执行按 `wa_users.id` / `eb_user.uid` 过滤,未从源 dump 导入缺失用户或改写当前库用户身份。
|
||||
|
||||
## 博森元团队补充迁移结果
|
||||
|
||||
- 已于 **2026-06-14** 从 `bsy-yangtangyoupin_2026-06-14_14-25-01_mysql_data.sql` 补迁博森元团队数据并 `COMMIT`。
|
||||
- 执行脚本:`docs/sql/run_com_bygsf212_bsy_supplement.py`
|
||||
- 执行前备份:`docs/sql/backups/bygsf212_bsy_supplement_before_20260614_213738.sql.gz`(已通过 `gzip -t` 校验)
|
||||
- 迁移策略:不覆盖当前目标库已存在的金雅文/当前用户;博森元中 ID 已被占用但手机号不同的用户分配新 `uid` / `wa_users.id`,并同步改写迁移数据中的 `user_id` / `uid` / `pid` / `spread_uid`。
|
||||
- 博森元用户 ID 映射:
|
||||
- `92688`:李霞 / `18118281551`(沿用原 ID)
|
||||
- `92904`:邓桂花 / `15951431026`(沿用原 ID)
|
||||
- `92964`:王平君 / `18796696663`(沿用原 ID)
|
||||
- `93164`:周爱平 / `15190438222`(沿用原 ID)
|
||||
- `93251 -> 93315`:乔秀勇 / `18136259551`
|
||||
- `93259 -> 93316`:郑仁风 / `18352718222`
|
||||
- `93273 -> 93317`:夏辉 / `18936239839`
|
||||
- `93272 -> 93318`:刘艾平 / `18724108815`
|
||||
- `93276 -> 93319`:韩玉霞 / `19281861596`
|
||||
- 本次补迁插入行数:
|
||||
- `wa_users`:9
|
||||
- `eb_user`:9
|
||||
- `wa_merchandise`:14
|
||||
- `wa_selfbonus_log`:673
|
||||
- `wa_sharebonus_log`:861
|
||||
- `wa_coupon_log`:146
|
||||
- `eb_user_integral_record`:679
|
||||
- 补迁后行数:
|
||||
- `wa_users`:23
|
||||
- `eb_user`:23
|
||||
- `wa_merchandise`:32
|
||||
- `wa_selfbonus_log`:1519
|
||||
- `wa_sharebonus_log`:1625
|
||||
- `wa_coupon_log`:319
|
||||
- `eb_user_integral_record`:1549
|
||||
- 复核:9 个博森元用户均已在 `wa_users` / `eb_user` 中;原冲突 ID 用户(如 `93251` 龚华侨、`93259` 薛春华等)仍保留;`eb_user_integral_record` 无孤儿 `uid`。
|
||||
- 备注:补迁脚本已处理复跑幂等;补迁完成后再次 dry-run 显示所有博森元手机号已存在,插入行数为 0。
|
||||
|
||||
## 移除冲突用户结果
|
||||
|
||||
- 已于 **2026-06-15** 清除当前库中 `龚华侨`、`杜紅梅/杜红梅`、`戴庆宏`、`陈晓平` 4 个用户相关数据并 `COMMIT`。
|
||||
- 清除用户:
|
||||
- `93251`:龚华侨 / `15952530725`
|
||||
- `93272`:杜紅梅 / `13952547832`
|
||||
- `93273`:戴庆宏 / `15000637090`
|
||||
- `93276`:陈晓平 / `15995103126`
|
||||
- 执行脚本:`docs/sql/run_com_bygsf212_remove_conflict_users.py`
|
||||
- 执行前备份:`docs/sql/backups/bygsf212_remove_93251_93272_93273_93276_before_20260615_085155.sql.gz`(已通过 `gzip -t` 校验)
|
||||
- 本次删除行数:
|
||||
- `wa_users`:4
|
||||
- `eb_user`:4
|
||||
- `wa_selfbonus_log`:31
|
||||
- `wa_sharebonus_log`:14
|
||||
- `wa_address`:4
|
||||
- `wa_alipay`:4
|
||||
- `eb_user_address`:4
|
||||
- `eb_user_bill`:2
|
||||
- `eb_user_experience_record`:2
|
||||
- `eb_user_integral_record`:33
|
||||
- `eb_user_visit_record`:2
|
||||
- 未命中需要清除的数据:`wa_order`、`wa_merchandise`、`wa_coupon_log`、`wa_withdraw`、`eb_store_order` 等为 0 行;无外部 `wa_users.pid` / `eb_user.spread_uid` 引用需要改挂。
|
||||
- 清除后复核:
|
||||
- 上述 4 个 `uid` 与手机号在 `wa_users` / `eb_user` 中均不存在。
|
||||
- 相关日志、地址、积分、访问记录表中均无这 4 个 `uid` 残留。
|
||||
- 博森元补迁后分配的新用户 `93315` 乔秀勇、`93316` 郑仁风、`93317` 夏辉、`93318` 刘艾平、`93319` 韩玉霞仍存在。
|
||||
- 清除后核心表行数:
|
||||
- `wa_users`:19
|
||||
- `eb_user`:19
|
||||
- `wa_merchandise`:32
|
||||
- `wa_selfbonus_log`:1488
|
||||
- `wa_sharebonus_log`:1611
|
||||
- `wa_coupon_log`:319
|
||||
- `eb_user_integral_record`:1516
|
||||
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 源数据 dump:
|
||||
- `/Users/mac/Works26/miao-july/宝应鼎信汇/bsy-yangtangyoupin_2026-06-14_14-25-01_mysql_data.sql`
|
||||
- `/Users/mac/Works26/miao-july/宝应鼎信汇/jyw-yangtangyoupin_2026-06-14_14-55-01_mysql_data.sql`
|
||||
- 团队成员信息:
|
||||
- `/Users/mac/Works26/miao-july/宝应鼎信汇/博森元团队成员信息表.xlsx`
|
||||
- `/Users/mac/Works26/miao-july/宝应鼎信汇/金雅文团队成员信息表.xlsx`
|
||||
65
docs/com-bygsf212.md
Normal file
65
docs/com-bygsf212.md
Normal file
@@ -0,0 +1,65 @@
|
||||
## 公司名称: 宝应桂圣富商贸/鼎信汇商贸
|
||||
|
||||
- host ip: 118.31.36.212
|
||||
|
||||
### **修改任务**
|
||||
|
||||
- 新建分支:bygsf212 ,合并分支"byhlc112"的最新代码到该分支,并根据上述信息修改相关需要变更项,使符合该新公司项目环境
|
||||
- 在新建分支下修改
|
||||
|
||||
---
|
||||
|
||||
### 相关配置
|
||||
|
||||
- mysql数据库使用阿里云rds:rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
- mysql rds中数据库名:bygsf212
|
||||
|
||||
- 积分商城地址:https://jf.b3y45.com
|
||||
- **云服务器积分商城目录**:/www/wwwroot/jf.b3y45.com
|
||||
- **云服务器jar存放目录**:/www/wwwroot/javaapi
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### backend/crmeb-front模块变更
|
||||
|
||||
- 1. profile: bygsf212
|
||||
- 2. profile file: application-bygsf212.yml, mysql连接信息修改,redis主机ip修改。
|
||||
- 3. **PDF合同模板文件路径**:pdf/sign_contract_bygsf212.pdf
|
||||
- 4. 用户PDF合同url地址前缀/落库域名:https://b3y45.com/
|
||||
- 5. imagePath: /www/wwwroot/b3y45.com/
|
||||
|
||||
### uniapp前端配置变更
|
||||
|
||||
- 1. 积分商城domain:https://jf.b3y45.com
|
||||
- 2. 抢购页面跳转地址:https://b3y45.com
|
||||
- 3. **PDF合同预览文件路径**: /static/sign_contract_bygsf212.pdf
|
||||
- 4. **手动使用HBuilder编译发布**
|
||||
|
||||
---
|
||||
|
||||
### backend/crmeb-admin模块变更
|
||||
|
||||
- 1. profile: bygsf212
|
||||
- 2. profile file: application-bygsf212.yml, mysql和redis主机ip修改,sync: source-id: shop_18, target-mer-id: 18
|
||||
|
||||
### 积分商城后台backend-adminend配置变更
|
||||
|
||||
- 1. backend-adminend/.env.development文件中VUE_APP_BASE_API改为https://jf.b3y45.com
|
||||
- 2. backend-adminend/.env.production文件中VUE_APP_BASE_API改为https://jf.b3y45.com
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 相关文件
|
||||
|
||||
、、、启动积分商城api服务
|
||||
cd /www/wwwroot/javaapi
|
||||
nohup java -Xms128m -Xmx256m -jar miao-front-2.2.jar > front.log & tail -f front.log
|
||||
、、、
|
||||
|
||||
、、、启动积分商城后台api服务
|
||||
cd /www/wwwroot/javaapi
|
||||
nohup java -Xms128m -Xmx256m -jar miao-admin-2.2.jar > admin.log & tail -f admin.log
|
||||
、、、
|
||||
61
docs/com-byhlc112-data-imgration.md
Normal file
61
docs/com-byhlc112-data-imgration.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 公司名称:宝应宏煜春商贸
|
||||
|
||||
host ip: 39.97.236.112
|
||||
|
||||
## mysql数据库配置信息
|
||||
|
||||
datasource:
|
||||
rds: rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
name: byhlc112
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
|
||||
## 数据清理任务
|
||||
|
||||
- **数据范围**:`wa_users.id` / `eb_user.uid` 保留名单:
|
||||
`92801, 93011, 93032, 93073, 93076, 93078, 93094, 93120, 93136, 93147, 93155, 93185, 93193, 93216, 93218, 93248, 93284, 93287, 93290, 93295, 93297, 93301, 93305, 93306, 93307, 93315, 93318, 93321, 93322, 93323, 93325, 93327, 93328, 93329`
|
||||
|
||||
- 保留wa_users表中id在用户id数据范围的 ,删除其余用户数据
|
||||
- 保留eb_user表中uid在用户id数据范围的 ,删除其余用户数据
|
||||
|
||||
- wa_order
|
||||
清空wa_order表中数据
|
||||
|
||||
- wa_merchandise
|
||||
从源数据dump文件中提取“created_at >= 2026-05-28”并且seller_id或buyer_id在用户id数据范围的寄售商品,删除其余数据
|
||||
(当前库表字段为 `user_id` 表示卖家,实现时按 `user_id` 与日期条件过滤。)
|
||||
|
||||
- wa_selfbonus_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_sharebonus_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_coupon_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_withdraw
|
||||
清空wa_withdraw表中数据
|
||||
|
||||
- eb_store_order
|
||||
清空eb_store_order表中数据
|
||||
|
||||
- eb_user_integral_record
|
||||
只保留用户在名单内的记录;表字段为 `uid`(与 `wa_users.id` / `eb_user.uid` 对应),实现按 `uid` 过滤。
|
||||
|
||||
## 执行结果
|
||||
|
||||
- 已于 **2026-05-30** 按当前保留名单执行清理并 `COMMIT`。
|
||||
- 结果:`wa_order`、`wa_withdraw`、`eb_store_order` 已清空。
|
||||
- 保留后行数:
|
||||
- `wa_users`:32
|
||||
- `eb_user`:32
|
||||
- `wa_selfbonus_log`:1285
|
||||
- `wa_sharebonus_log`:1536
|
||||
- `wa_coupon_log`:180
|
||||
- `eb_user_integral_record`:1321
|
||||
- `wa_merchandise`:35
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 新公司初始会员信息: '/Users/mac/Works26/miao-july/byhlc/新团队成员名单.xlsx'
|
||||
59
docs/com-byhlc112.md
Normal file
59
docs/com-byhlc112.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## 公司名称: 宝应宏煜春商贸, host ip: 39.97.236.112
|
||||
|
||||
|
||||
### **修改任务**
|
||||
|
||||
- 新建分支byhlc112,合并byhlc112分支的最新代码到该分支,并根据上述信息修改相关需要变更项,使符合该新公司项目环境
|
||||
- 在新建分支下修改
|
||||
|
||||
---
|
||||
|
||||
### mysql数据库配置
|
||||
|
||||
- mysql数据库使用阿里云rds:rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
- rds中项目数据库名:byhlc112
|
||||
|
||||
---
|
||||
|
||||
### backend/crmeb-front模块变更
|
||||
|
||||
- 1. profile: byhlc112
|
||||
- 2. profile file: application-byhlc112.yml, mysql连接信息修改,redis主机ip修改。
|
||||
- 3. **PDF合同模板文件路径**:pdf/sign_contract_byhlc112.pdf
|
||||
- 4. 用户PDF合同url地址前缀/落库域名:https://h5y2c.com/
|
||||
- 5. imagePath: /www/wwwroot/h5y2c.com/
|
||||
|
||||
### uniapp前端配置变更
|
||||
|
||||
- 1. 积分商城domain:https://jf.h5y2c.com
|
||||
- 2. 抢购页面跳转地址:https://h5y2c.com
|
||||
- 3. **PDF合同预览文件路径**: /static/sign_contract_byhlc112.pdf
|
||||
|
||||
|
||||
---
|
||||
|
||||
### backend/crmeb-admin模块变更
|
||||
|
||||
- 1. profile: byhlc112
|
||||
- 2. profile file: application-byhlc112.yml, mysql和redis主机ip修改,sync: source-id: shop_16, target-mer-id: 16
|
||||
|
||||
### 积分商城后台backend-adminend配置变更
|
||||
|
||||
- 1. backend-adminend/.env.development文件中VUE_APP_BASE_API改为https://jf.h5y2c.com
|
||||
- 2. backend-adminend/.env.production文件中VUE_APP_BASE_API改为https://jf.h5y2c.com
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 相关文件
|
||||
|
||||
、、、启动积分商城api服务
|
||||
cd /www/wwwroot/javaapi
|
||||
nohup java -Xms128m -Xmx256m -jar miao-front-2.2.jar > front.log & tail -f front.log
|
||||
、、、
|
||||
|
||||
、、、启动积分商城后台api服务
|
||||
cd /www/wwwroot/javaapi
|
||||
nohup java -Xms128m -Xmx256m -jar miao-admin-2.2.jar > admin.log & tail -f admin.log
|
||||
、、、
|
||||
121
docs/com-czcf82-data-delete-0601.md
Normal file
121
docs/com-czcf82-data-delete-0601.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 公司名称:池州春芳商贸
|
||||
|
||||
## mysql数据库配置信息
|
||||
|
||||
host ip: 121.43.134.82
|
||||
datasource:
|
||||
name: yangtangyoupin
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
|
||||
|
||||
## 数据清理任务
|
||||
|
||||
- **数据范围**:用户 id 集(`eb_user.uid` 与 `wa_users.id` 一致):92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251
|
||||
|
||||
- 说明:之前截图中的 C/D/E 层级用户已与本次 H/K 层级名单按 `uid` 合并,当前范围为唯一 `uid` 集。
|
||||
|
||||
| 姓名 | 层级 | 用户ID | 上级ID | 账号 |
|
||||
| --- | --- | ---: | ---: | --- |
|
||||
| 徐圣方 | H | 93106 | 93092 | 15249924088 |
|
||||
| 周祥 | H | 93210 | 93106 | 18095665910 |
|
||||
| 江玲 | H | 93217 | 93092 | 18156673857 |
|
||||
| 江燕 | H | 93216 | 93092 | 18056699143 |
|
||||
| 周松琴 | H | 93248 | 93210 | 13395663023 |
|
||||
| 黄秀珍 | H | 93185 | 93169 | 13801810233 |
|
||||
| 袁清弟 | H | 93182 | 93169 | 18016367923 |
|
||||
| 张本汉 | H | 93205 | 93124 | 13965915911 |
|
||||
| 吴兆燕 | H | 93204 | 93124 | 13615660918 |
|
||||
| 吴海根 | H | 93200 | 93171 | 15655976789 |
|
||||
| 陈巧玲 | H | 93207 | 93124 | 18855619191 |
|
||||
| 吴美云 | H | 93213 | 93203 | 18056682196 |
|
||||
| 朱华兵 | H | 93242 | 93203 | 17356611916 |
|
||||
| 王传 | H | 93251 | 93124 | 18956675871 |
|
||||
| 汪占云 | H | 93227 | 93167 | 15249909910 |
|
||||
| 潘运生 | H | 93222 | 93167 | 18656668096 |
|
||||
| 陆惊蕾 | H | 93119 | 93105 | 15905662876 |
|
||||
| 徐池英 | H | 93134 | 93119 | 18715478619 |
|
||||
| 董金权 | H | 93156 | 93119 | 13905663559 |
|
||||
| 吴可佳 | H | 93173 | 93134 | 15715669883 |
|
||||
| 薛丽萍 | H | 93187 | 93119 | 18905666240 |
|
||||
| 陶会丽 | H | 93188 | 93119 | 18056643607 |
|
||||
| 朱向东 | H | 93196 | 93119 | 18956692810 |
|
||||
| 李华 | H | 93198 | 93119 | 13865669670 |
|
||||
| 胡贵宾 | H | 93190 | 93133 | 13866451818 |
|
||||
| 纪贞凤 | H | 93214 | 93156 | 13856695745 |
|
||||
| 曹四清 | H | 93220 | 93134 | 15955660777 |
|
||||
| 舒妍 | H | 93212 | 93187 | 17730497790 |
|
||||
| 陶丽芳 | H | 93221 | 93188 | 15385460258 |
|
||||
| 余绍光 | H | 93226 | 93173 | 13956891985 |
|
||||
| 方云云 | H | 93233 | 93198 | 13965929979 |
|
||||
| 刘根枝 | H | 93235 | 93190 | 17756625293 |
|
||||
| 汪能学 | H | 93245 | 93119 | 13965941820 |
|
||||
| 何小伍 | H | 93240 | 93119 | 13093492768 |
|
||||
| 王芳 | H | 93132 | 93119 | 15956625685 |
|
||||
| 章旭东 | H | 93166 | 93132 | 13365666600 |
|
||||
| 张乔林 | H | 93183 | 93132 | 18156617167 |
|
||||
| 许恩情 | H | 93172 | 93132 | 15856641370 |
|
||||
| 芮爱梅 | H | 93197 | 93132 | 13905669269 |
|
||||
| 宁东梅 | H | 93195 | 93132 | 18056647260 |
|
||||
| 王珍 | H | 93193 | 93132 | 13355668168 |
|
||||
| 胡春曲 | H | 93209 | 93172 | 15856637686 |
|
||||
| 郑雁 | H | 93211 | 93132 | 18956692260 |
|
||||
| 汤亚丽 | H | 93218 | 93193 | 18056635288 |
|
||||
| 金花鸡 | H | 93215 | 93193 | 18005663869 |
|
||||
| 宁美琴 | H | 93224 | 93195 | 13645665392 |
|
||||
| 张晓玲 | H | 93239 | 93183 | 18256624325 |
|
||||
| 杨艳霞 | H | 93230 | 93215 | 18856611108 |
|
||||
| 邓本琼 | H | 93133 | 93119 | 13868989499 |
|
||||
| 张丽 | H | 93163 | 93133 | 15805662996 |
|
||||
| 洪安荣 | H | 93191 | 93133 | 18005666645 |
|
||||
| 章丽君 | H | 93225 | 93163 | 13645665392 |
|
||||
| 侯美枝 | H | 93199 | 93133 | 19855762520 |
|
||||
| 胡红琴 | K | 93171 | 93105 | 15212465866 |
|
||||
| 钟真发 | K | 93208 | 93171 | 15305599997 |
|
||||
| 洪军 | K | 93231 | 93208 | 18955917212 |
|
||||
| 王兵启 | K | 92827 | 93195 | 18956691177 |
|
||||
| 胡晓彩 | K | 92738 | 92544 | 17756627812 |
|
||||
| 柯美燕 | K | 93140 | 92827 | 13635661721 |
|
||||
| 王珍华 | K | 93150 | 92738 | 13856372733 |
|
||||
|
||||
- 删除用户数据范围内的如下表的相关数据,同时做好数据备份
|
||||
- wa_users表中id在用户id数据范围的
|
||||
- eb_user表中uid在用户id数据范围的
|
||||
- wa_order
|
||||
- wa_merchandise
|
||||
- wa_selfbonus_log
|
||||
- wa_sharebonus_log
|
||||
- wa_coupon_log
|
||||
- wa_withdraw
|
||||
- eb_store_order
|
||||
- eb_user_integral_record
|
||||
|
||||
|
||||
|
||||
## 执行脚本
|
||||
|
||||
- `docs/sql/com-czcf82-data-delete-0601.sql`
|
||||
- `docs/sql/com-czcf82-data-delete-0601-2.sql`
|
||||
|
||||
## 执行结果
|
||||
|
||||
- 已于 2026-06-01 执行完成(首次截图范围 31 人;当前数据范围已按后续截图合并,旧 C/D/E 与新 H/K 以唯一 `uid` 口径汇总)。
|
||||
- 清理后回查结果:各目标表中对应用户数据均为 0 条。
|
||||
- 备份表已创建,行数分别为:
|
||||
- `wa_users_bak_20260601_170641`:31
|
||||
- `eb_user_bak_20260601_170641`:31
|
||||
- `wa_order_bak_20260601_170641`:975
|
||||
- `wa_merchandise_bak_20260601_170641`:675
|
||||
- `wa_selfbonus_log_bak_20260601_170641`:818
|
||||
- `wa_sharebonus_log_bak_20260601_170641`:793
|
||||
- `wa_coupon_log_bak_20260601_170641`:194
|
||||
- `wa_withdraw_bak_20260601_170641`:9
|
||||
- `eb_store_order_bak_20260601_170641`:88
|
||||
- `eb_user_integral_record_bak_20260601_170641`:932
|
||||
- 已于 2026-06-01 完成第二轮清理,新增范围对应数据回查也均为 0 条。
|
||||
- 第二轮备份表已创建,后缀为 `20260601_210625`。
|
||||
|
||||
|
||||
## 相关文件
|
||||
|
||||
-
|
||||
80
docs/com-sqszx202-data-imgration.md
Normal file
80
docs/com-sqszx202-data-imgration.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 公司名称:宿迁盛泽鑫商贸
|
||||
|
||||
- host ip: 59.110.91.202
|
||||
|
||||
## mysql数据库配置信息
|
||||
|
||||
- datasource:
|
||||
rds: rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
name: sqszx202
|
||||
username: yangtangyoupin
|
||||
password: 5Fn8eWrbYFtAhCZw
|
||||
|
||||
|
||||
## 数据清理任务
|
||||
|
||||
- **用户数据范围**:`wa_users.id` / `eb_user.uid` 保留名单:
|
||||
`93164, 93133, 93132, 93113, 93216, 93230, 93238, 93258, 93277, 93283, 93284, 93291, 93293, 93308, 93299, 93290, 93280, 93279, 93255, 93252, 93251, 93250, 93249, 93248, 93223, 93221, 93111, 93099, 93263, 93265, 93272, 93270, 93269, 93268, 93267, 93266, 93276, 93278, 93286, 93296, 93309, 93281, 93275, 93271, 93139, 93264, 93247, 93218, 93217, 93194, 93142`
|
||||
|
||||
- 保留wa_users表中id在用户id数据范围的 ,删除其余用户数据
|
||||
- 保留eb_user表中uid在用户id数据范围的 ,删除其余用户数据
|
||||
|
||||
- wa_order
|
||||
清空wa_order表中数据
|
||||
|
||||
- wa_merchandise
|
||||
保留数据库中“created_at >= 2026-06-12”并且seller_id或buyer_id在用户id数据范围的寄售商品,删除其余数据
|
||||
(当前库表字段为 `user_id` 表示卖家,实现时按 `user_id` 与日期条件过滤。)
|
||||
|
||||
- wa_selfbonus_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_sharebonus_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_coupon_log
|
||||
只保留 `user_id` 在用户id数据范围内的记录,删除其余数据
|
||||
|
||||
- wa_withdraw
|
||||
清空wa_withdraw表中数据
|
||||
|
||||
- eb_store_order
|
||||
清空eb_store_order表中数据
|
||||
|
||||
- eb_user_integral_record
|
||||
只保留用户在名单内的记录;表字段为 `uid`(与 `wa_users.id` / `eb_user.uid` 对应),实现按 `uid` 过滤。
|
||||
|
||||
## 执行结果
|
||||
|
||||
- 已于 **2026-06-14** 按当前保留名单执行清理并 `COMMIT`。
|
||||
- 执行脚本:`docs/sql/run_com_sqszx202_cleanup.py`
|
||||
- 执行前备份:`docs/sql/backups/sqszx202_cleanup_before_20260614_090658.sql.gz`(已通过 `gzip -t` 校验)
|
||||
- `wa_merchandise` 从源 dump 解析结果:dump 中共 3156 行,满足 `created_at >= 2026-06-12` 且 `user_id` 在名单内的保留商品为 54 行。
|
||||
- 删除行数:
|
||||
- `wa_order`:3168
|
||||
- `wa_withdraw`:171
|
||||
- `eb_store_order`:348
|
||||
- `wa_merchandise`:3102
|
||||
- `wa_selfbonus_log`:2255
|
||||
- `wa_sharebonus_log`:2514
|
||||
- `wa_coupon_log`:208
|
||||
- `eb_user_integral_record`:2343
|
||||
- `eb_user`:79
|
||||
- `wa_users`:79
|
||||
- 保留后行数:
|
||||
- `wa_order`:0
|
||||
- `wa_withdraw`:0
|
||||
- `eb_store_order`:0
|
||||
- `wa_merchandise`:54
|
||||
- `wa_selfbonus_log`:1644
|
||||
- `wa_sharebonus_log`:1791
|
||||
- `wa_coupon_log`:8
|
||||
- `eb_user_integral_record`:1993
|
||||
- `eb_user`:51
|
||||
- `wa_users`:51
|
||||
- 复核:清理后再次 dry-run,以上表剩余待删除行数均为 0。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 新公司初始会员信息: '/Users/mac/Works26/miao-july/宿迁盛泽鑫/盛泽鑫团队成员信息表.xlsx'
|
||||
- 源数据dump文件: '/Users/mac/Works26/miao-july/宿迁盛泽鑫/anpengran-yangtangyoupin_2026-06-14_02-15-02_mysql_data.sql'
|
||||
64
docs/com-sqszx202.md
Normal file
64
docs/com-sqszx202.md
Normal file
@@ -0,0 +1,64 @@
|
||||
## 公司名称: 宿迁盛泽鑫商贸
|
||||
|
||||
- host ip: 59.110.91.202
|
||||
|
||||
### **修改任务**
|
||||
|
||||
- 新建分支:sqszx202 ,合并分支:byhlc112 的最新代码到该分支,并根据上述信息修改相关需要变更项,使符合该新公司项目环境
|
||||
- 在新建分支下修改
|
||||
|
||||
---
|
||||
|
||||
### 相关配置
|
||||
|
||||
- mysql数据库使用阿里云rds:rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
- rds中项目数据库名:sqszx202
|
||||
|
||||
- 积分商城地址:https://jf.j3s4s5.com
|
||||
- **云服务器积分商城目录**:/www/wwwroot/jf.j3s4s5.com
|
||||
- **云服务器jar存放目录**:/www/wwwroot/javaapi
|
||||
|
||||
---
|
||||
|
||||
### backend/crmeb-front模块变更
|
||||
|
||||
- 1. profile: sqszx202
|
||||
- 2. profile file: application-sqszx202.yml, mysql连接信息修改,redis主机ip修改。
|
||||
- 3. **PDF合同模板文件路径**:pdf/sign_contract_sqszx202.pdf
|
||||
- 4. 用户PDF合同url地址前缀/落库域名:https://j3s4s5.com/
|
||||
- 5. imagePath: /www/wwwroot/j3s4s5.com/
|
||||
|
||||
### uniapp前端配置变更
|
||||
|
||||
- 1. 积分商城domain:https://jf.j3s4s5.com
|
||||
- 2. 抢购页面跳转地址:https://j3s4s5.com
|
||||
- 3. **PDF合同预览文件路径**: /static/sign_contract_sqszx202.pdf
|
||||
|
||||
|
||||
---
|
||||
|
||||
### backend/crmeb-admin模块变更
|
||||
|
||||
- 1. profile: sqszx202
|
||||
- 2. profile file: application-sqszx202.yml, mysql和redis主机ip修改,sync: source-id: shop_17, target-mer-id: 17
|
||||
|
||||
### 积分商城后台backend-adminend配置变更
|
||||
|
||||
- 1. backend-adminend/.env.development文件中VUE_APP_BASE_API改为https://jf.j3s4s5.com
|
||||
- 2. backend-adminend/.env.production文件中VUE_APP_BASE_API改为https://jf.j3s4s5.com
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 相关文件
|
||||
|
||||
、、、启动积分商城api服务
|
||||
cd /www/wwwroot/javaapi
|
||||
nohup java -Xms128m -Xmx256m -jar miao-front-2.2.jar > front.log & tail -f front.log
|
||||
、、、
|
||||
|
||||
、、、启动积分商城后台api服务
|
||||
cd /www/wwwroot/javaapi
|
||||
nohup java -Xms128m -Xmx256m -jar miao-admin-2.2.jar > admin.log & tail -f admin.log
|
||||
、、、
|
||||
BIN
docs/contract/sign_contract_bygsf212.docx
Normal file
BIN
docs/contract/sign_contract_bygsf212.docx
Normal file
Binary file not shown.
BIN
docs/contract/sign_contract_bygsf212.pdf
Normal file
BIN
docs/contract/sign_contract_bygsf212.pdf
Normal file
Binary file not shown.
BIN
docs/contract/sign_contract_sqszx202.docx
Normal file
BIN
docs/contract/sign_contract_sqszx202.docx
Normal file
Binary file not shown.
BIN
docs/contract/sign_contract_sqszx202.pdf
Normal file
BIN
docs/contract/sign_contract_sqszx202.pdf
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,47 +0,0 @@
|
||||
# 积分商城front MySQL 远程连接汇总
|
||||
|
||||
整理范围:`backend/crmeb-front/src/main/resources/application*.yml`
|
||||
|
||||
整理时间:2026-05-11
|
||||
|
||||
## 口径说明
|
||||
|
||||
- 仅汇总 MySQL URL 中 host 为远程地址的配置。
|
||||
- 所属公司优先按 `docs/company-info-*.md`、`docs/*data-imgration*.md` 中的公司名称和 host ip 匹配;没有明确公司名时,按部署文档、域名、profile 或数据库名推断并注明。
|
||||
- 密码按当前配置文件完整记录。
|
||||
|
||||
## 按 MySQL 主机聚合
|
||||
|
||||
| MySQL host:port | 关联 profile | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `8.140.218.149:3306` | `byjyw149` | 宝应金雅文商贸 |
|
||||
| `8.136.120.231:3306` | `czc231` | 宝应晨召春商贸 |
|
||||
| `121.43.134.82:3306` | `czcf82` | 池州春芳商贸 |
|
||||
| `101.37.101.6:3306` | `czrt6` | 池州瑞棠商贸 |
|
||||
| `114.55.232.191:3306` | `hapr191` | 淮安鹏然商贸 |
|
||||
| `106.14.132.80:3306` | `sxsy80` | 太原树英商贸 |
|
||||
| `39.106.63.33:3306` | `miao33` | 夏盛军商贸 |
|
||||
| `123.56.214.80:3306` | `miao80` | 宝应博森元 |
|
||||
| `101.37.253.50:3306` | `miao50` | 上海文锦惠商贸 |
|
||||
| `101.132.245.153:3306` | `shjjy153` | 上海聚伽源商贸 |
|
||||
| `182.92.78.159:3306` | `shccd159` | 上海慈初德商贸 |
|
||||
|
||||
## 远程 MySQL 配置清单
|
||||
|
||||
| Profile | 配置文件 | 所属公司 / 项目 | MySQL host:port | 数据库名 | 用户名 | 密码 | 依据 / 备注 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| `byjyw149` | `backend/crmeb-front/src/main/resources/application-byjyw149.yml` | 宝应金雅文商贸 | `8.140.218.149:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/company-info-byjyw149.md`、`docs/byjyw149-data-imgration.md` |
|
||||
| `czc231` | `backend/crmeb-front/src/main/resources/application-czc231.yml` | 宝应晨召春商贸 | `8.136.120.231:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/company-czc231-data-imgration.md`、`docs/company-czc231-integral-imgration.md` |
|
||||
| `czcf82` | `backend/crmeb-front/src/main/resources/application-czcf82.yml` | 池州春芳商贸 | `121.43.134.82:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/company-info-czcf82.md`、`docs/company-czcf82-data-imgration.md` |
|
||||
| `czrt6` | `backend/crmeb-front/src/main/resources/application-czrt6.yml` | 池州瑞棠商贸 | `101.37.101.6:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/company-info-czrt6.md`、`docs/company-czrt6-data-imgration.md` |
|
||||
| `hapr191` | `backend/crmeb-front/src/main/resources/application-hapr191.yml` | 淮安鹏然商贸 | `114.55.232.191:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/company-info.md`、`docs/company-data-imgration.md` |
|
||||
| `sxsy80` | `backend/crmeb-front/src/main/resources/application-sxsy80.yml` | 太原树英商贸 | `106.14.132.80:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/com-sxsy80.md`、`docs/com-sxsy80-data-imgration.md` |
|
||||
| `miao33` | `backend/crmeb-front/src/main/resources/application-miao33.yml` | 夏盛军商贸 | `39.106.63.33:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/com-xsj33-data-imgration.md`;部署文档中也标注 `jfadmin.xiashengjun.com` |
|
||||
| `miao80` | `backend/crmeb-front/src/main/resources/application-miao80.yml` | 宝应博森元 | `123.56.214.80:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `backend-adminend/DEPLOY.md` 中 by80 示例域名为 `jfadmin.bosenyuan.com`;`.cursor/plans/bybsy80范围数据删除_1cf340f6.plan.md` 也指向该 host |
|
||||
| `miao50` | `backend/crmeb-front/src/main/resources/application-miao50.yml` | 上海文锦惠商贸 | `101.37.253.50:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `backend/DEPLOY.md`、OpenClaw 配置文档中标注为生产环境 |
|
||||
| `shjjy153` | `backend/crmeb-front/src/main/resources/application-shjjy153.yml` | 上海聚伽源商贸 | `101.132.245.153:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/compare-shjjy153-shccd159.md` 标注域名 `jjy-jf.fwxgpt.com`、`jjy-jfadmin.fwxgpt.com` |
|
||||
| `shccd159` | `backend/crmeb-front/src/main/resources/application-shccd159.yml` | 上海慈初德商贸 | `182.92.78.159:3306` | `yangtangyoupin` | `yangtangyoupin` | `5Fn8eWrbYFtAhCZw` | `docs/compare-shjjy153-shccd159.md` 标注域名 `ccd-jf.fwxgpt.com`、`ccd-jfadmin.fwxgpt.com` |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
# 积分模块新增页面 — 功能测试报告 v2
|
||||
|
||||
**测试时间:** 2026-03-31
|
||||
**测试范围:** Coding Plan 交付清单功能验证(静态分析 + 结构检查)
|
||||
**测试结果:** ✅ 全部通过(11/11 项)
|
||||
|
||||
---
|
||||
|
||||
## T01 — 交付文件存在性检查
|
||||
|
||||
| 文件 | 结果 |
|
||||
|---|:---:|
|
||||
| `src/layout/EmptyLayout.vue` | ✅ PASS |
|
||||
| `src/utils/requestNoAuth.js` | ✅ PASS |
|
||||
| `src/router/modules/integralExternal.js` | ✅ PASS |
|
||||
| `src/router/index.js`(已注册) | ✅ PASS |
|
||||
| `src/api/integralExternal.js` | ✅ PASS |
|
||||
| `src/permission.js`(已修改) | ✅ PASS |
|
||||
| `src/filters/user.js`(已修改) | ✅ PASS |
|
||||
| `src/views/integral-external/order/index.vue` | ✅ PASS |
|
||||
| `src/views/integral-external/user/index.vue` | ✅ PASS |
|
||||
| `src/views/integral-external/user-integral-detail/index.vue` | ✅ PASS |
|
||||
| `ExternalIntegralController.java` | ✅ PASS |
|
||||
|
||||
**11/11 文件存在**
|
||||
|
||||
---
|
||||
|
||||
## T02 — permission.js 白名单前缀检查
|
||||
|
||||
```js
|
||||
const whiteList = ['/login', '/auth-redirect'];
|
||||
const whiteListPrefixes = ['/integral-external'];
|
||||
// ...
|
||||
if (whiteList.indexOf(to.path) !== -1
|
||||
|| whiteListPrefixes.some(prefix => to.path.startsWith(prefix))) {
|
||||
next();
|
||||
}
|
||||
```
|
||||
|
||||
- ✅ `whiteListPrefixes` 已定义并包含 `/integral-external`
|
||||
- ✅ 使用 `startsWith` 前缀匹配(支持所有子路径)
|
||||
|
||||
---
|
||||
|
||||
## T03 — router/index.js 注册检查
|
||||
|
||||
- ✅ `import integralExternalRouter from './modules/integralExternal'` 已添加
|
||||
- ✅ `integralExternalRouter` 已加入 `constantRoutes`
|
||||
|
||||
---
|
||||
|
||||
## T04 — 新页面无权限指令检查
|
||||
|
||||
| 页面 | v-hasPermi | checkPermi |
|
||||
|---|:---:|:---:|
|
||||
| order/index.vue | ✅ 无 | ✅ 无 |
|
||||
| user/index.vue | ✅ 无 | ✅ 无 |
|
||||
| user-integral-detail/index.vue | ✅ 无 | ✅ 无 |
|
||||
|
||||
**三个页面均不含任何权限指令,符合免认证要求。**
|
||||
|
||||
---
|
||||
|
||||
## T05 — phoneDesensitize 过滤器链路
|
||||
|
||||
1. ✅ `filters/user.js` 导出 `phoneDesensitize` 函数
|
||||
2. ✅ `filters/index.js` 通过 `export * from './user'` 自动 re-export
|
||||
3. ✅ `main.js` 通过 `Object.keys(filters).forEach` 全局注册所有过滤器
|
||||
4. ✅ `user/index.vue` 正确使用 `{{ scope.row.phone | phoneDesensitize }}`
|
||||
|
||||
---
|
||||
|
||||
## T06 — API 函数与后端路径一致性
|
||||
|
||||
| API 函数 | 前端 URL | HTTP 方法 |
|
||||
|---|---|:---:|
|
||||
| `getExternalOrderList` | `external/integral/order/list` | GET |
|
||||
| `getExternalUserList` | `external/integral/user/list` | GET |
|
||||
| `getExternalIntegralLog` | `external/integral/log/list` | POST |
|
||||
|
||||
所有 URL 与 `ExternalIntegralController` 中的映射路径完全一致。
|
||||
|
||||
---
|
||||
|
||||
## T07 — 文件语法结构检查
|
||||
|
||||
| 文件 | template | script | name 属性 | 括号平衡 |
|
||||
|---|:---:|:---:|:---:|:---:|
|
||||
| EmptyLayout.vue | ✅ | ✅ | ✅ | ✅ |
|
||||
| order/index.vue | ✅ | ✅ | ✅ | ✅ |
|
||||
| user/index.vue | ✅ | ✅ | ✅ | ✅ |
|
||||
| user-integral-detail/index.vue | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## T08 — 路由路径一致性
|
||||
|
||||
| 路由定义(子路径) | 完整路径 | 跳转来源 |
|
||||
|---|---|---|
|
||||
| `order` | `/integral-external/order` | 默认 redirect |
|
||||
| `user` | `/integral-external/user` | — |
|
||||
| `user/integral-detail` | `/integral-external/user/integral-detail` | user/index.vue `$router.push` |
|
||||
|
||||
- ✅ `user/index.vue` 导航路径 `/integral-external/user/integral-detail` 与路由定义一致
|
||||
|
||||
---
|
||||
|
||||
## T09 — EmptyLayout 引用链
|
||||
|
||||
- ✅ `integralExternal.js` 动态引入 `EmptyLayout`
|
||||
- ✅ `EmptyLayout.vue` 包含 `<router-view />`(子页面正确渲染)
|
||||
|
||||
---
|
||||
|
||||
## T10 — requestNoAuth 免认证验证
|
||||
|
||||
- ✅ `api/integralExternal.js` 使用 `requestNoAuth` 实例(非 `request`)
|
||||
- ✅ `requestNoAuth.js` 请求拦截器中**无**任何 `Authorization` Header 注入逻辑
|
||||
- ✅ `requestNoAuth.js` 响应拦截器中**无** 401 重定向到登录页逻辑
|
||||
|
||||
---
|
||||
|
||||
## T11 — 后端 Java 检查
|
||||
|
||||
| 检查项 | 结果 |
|
||||
|---|:---:|
|
||||
| `@RestController` 注解 | ✅ PASS |
|
||||
| `@RequestMapping("api/external/integral")` | ✅ PASS |
|
||||
| `/order/list` → `@GetMapping` | ✅ PASS(与前端 GET 一致) |
|
||||
| `/user/list` → `@GetMapping` | ✅ PASS(与前端 GET 一致) |
|
||||
| `/log/list` → `@PostMapping` | ✅ PASS(与前端 POST 一致) |
|
||||
| **无 `@PreAuthorize`** | ✅ PASS |
|
||||
| `WebSecurityConfig` permitAll 白名单 | ✅ PASS |
|
||||
|
||||
---
|
||||
|
||||
## 汇总
|
||||
|
||||
| 测试项 | 通过 | 失败 |
|
||||
|---|:---:|:---:|
|
||||
| T01 文件存在性(11项) | 11 | 0 |
|
||||
| T02 路由白名单前缀 | 1 | 0 |
|
||||
| T03 路由注册 | 1 | 0 |
|
||||
| T04 无权限指令(3页) | 3 | 0 |
|
||||
| T05 过滤器链路(4环节) | 4 | 0 |
|
||||
| T06 API 路径一致性(3接口) | 3 | 0 |
|
||||
| T07 文件语法结构(4文件) | 4 | 0 |
|
||||
| T08 路由路径一致性 | 1 | 0 |
|
||||
| T09 EmptyLayout 引用链 | 2 | 0 |
|
||||
| T10 免认证验证(3项) | 3 | 0 |
|
||||
| T11 后端 Java(7项) | 7 | 0 |
|
||||
| **合计** | **40** | **0** |
|
||||
|
||||
> ✅ **40/40 全部通过** — 交付物满足 Coding Plan 所有功能需求,可进入联调阶段。
|
||||
|
||||
---
|
||||
|
||||
## 待联调验证(需运行环境)
|
||||
|
||||
以下项目需在实际启动前后端后验证:
|
||||
|
||||
- [ ] 浏览器访问 `/integral-external/order` 不跳转登录页
|
||||
- [ ] 订单列表数据正确渲染(含商品图片)
|
||||
- [ ] 用户列表手机号脱敏显示(138\*\*\*\*5678)
|
||||
- [ ] 点击"查看积分明细"正确传参 uid 并跳转
|
||||
- [ ] 积分明细页概览卡片显示正确的积分 & 个人奖金
|
||||
- [ ] 返回按钮回到用户积分列表
|
||||
@@ -1,169 +0,0 @@
|
||||
# 积分模块新增页面 — 测试报告
|
||||
|
||||
> 执行时间:2026-03-30
|
||||
> 测试类型:静态代码分析(新增页面尚未开发,针对现有代码库做预检)
|
||||
> 测试依据:integral-pages-coding-plan.md § 8 测试方案
|
||||
|
||||
---
|
||||
|
||||
## 总体结论
|
||||
|
||||
| 维度 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 新增页面文件 | ❌ 未创建 | 三个新页面均未开发,开发尚未启动 |
|
||||
| 免登录基础设施 | ❌ 未实现 | `permission.js` / `EmptyLayout` / `requestNoAuth` 均未修改 |
|
||||
| 参考页面可裁剪性 | ✅ 可行 | 原页面结构清晰,具备裁剪条件 |
|
||||
| 后端接口认证机制 | ⚠️ 有阻塞 | 积分接口有 `@PreAuthorize` 强认证,需后端配合新增免认证路径 |
|
||||
|
||||
---
|
||||
|
||||
## A 组:免登录访问测试
|
||||
|
||||
> 前提:`EmptyLayout.vue` / `requestNoAuth.js` / 路由 / `permission.js` 白名单均**尚未修改**
|
||||
|
||||
| 编号 | 测试场景 | 结果 | 详情 |
|
||||
|------|---------|------|------|
|
||||
| A-01 | 无 token 访问积分订单页 | ❌ **FAIL** | `permission.js` 白名单仅含 `['/login', '/auth-redirect']`,精确 `indexOf` 匹配,`/integral-external/order` 会被重定向至 `/login` |
|
||||
| A-02 | 无 token 访问用户积分页 | ❌ **FAIL** | 同 A-01,无对应白名单条目 |
|
||||
| A-03 | 无 token 访问积分明细页 | ❌ **FAIL** | 同 A-01 |
|
||||
| A-04 | 免登录页面不影响原有认证 | ✅ **PASS** | 原有 `/order/index` 等路径未做变更,仍需登录 |
|
||||
| A-05 | 已登录用户访问免登录页面 | ⏭️ **SKIP** | 新页面路由未注册,无法访问 |
|
||||
|
||||
**A 组结论**:需在 `permission.js` 第 21 行修改白名单,并将第 59 行 `indexOf` 改为 `startsWith` 前缀匹配。
|
||||
|
||||
**修改方案**:
|
||||
```js
|
||||
// permission.js 第 21 行
|
||||
const whiteList = ['/login', '/auth-redirect', '/integral-external'];
|
||||
|
||||
// 第 59 行
|
||||
if (whiteList.some(path => to.path.startsWith(path))) {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## B 组:积分订单页面测试
|
||||
|
||||
> 参考文件:`src/views/order/index.vue`(1182 行)
|
||||
|
||||
| 编号 | 测试场景 | 结果 | 详情 |
|
||||
|------|---------|------|------|
|
||||
| B-01 | 默认加载 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-02 | 按订单状态筛选 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-03 | 按时间范围筛选 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-04 | 按订单号搜索 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-05 | 重置筛选条件 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-06 | 分页切换 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-07 | 空数据状态 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| B-08 | 无操作列 | ⚠️ **PRE-CHECK** | 原页面含 **11 处** `v-hasPermi`、`发货/退款/出库` 操作按钮、导出功能,裁剪时需逐一清理 |
|
||||
|
||||
**B 组预检发现**:
|
||||
- `v-hasPermi` 出现 11 次,需全部移除
|
||||
- 导出按钮在第 79 行:`<el-button @click="exports" v-hasPermi="['admin:export:excel:order']">导出</el-button>`
|
||||
- `exports()` 方法在第 896 行,需连同方法一起删除
|
||||
- 原页面**无 Vuex store 直接依赖**,裁剪负担较轻
|
||||
|
||||
---
|
||||
|
||||
## C 组:用户积分页面测试
|
||||
|
||||
> 参考文件:`src/views/user/list/index.vue`(1079 行)
|
||||
|
||||
| 编号 | 测试场景 | 结果 | 详情 |
|
||||
|------|---------|------|------|
|
||||
| C-01 | 默认加载 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| C-02 | wa_users 字段展示 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| C-03 | 积分字段来源验证 | ⚠️ **PRE-CHECK** | `integral` 字段已在原 `user/list` 表格中(第 227 行),`eb_user.integral` 字段存在(`User.java` 第 98 行),来源正确 |
|
||||
| C-04 | wa_users 无关联数据 | ⚠️ **PRE-CHECK** | admin 端无现成的 wa_users API,需前端补充处理空值逻辑 |
|
||||
| C-05 | 用户搜索 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| C-06 | 跳转积分明细 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| C-07 | 分页功能 | ⏭️ **SKIP** | 页面未创建 |
|
||||
| C-08 | 无权限指令残留 | ⚠️ **PRE-CHECK** | 原页面含 **15 处** `v-hasPermi`,裁剪时均需移除 |
|
||||
|
||||
**C 组预检发现**:
|
||||
- `integral` 字段已在原用户列表接口中返回,**无需后端改动**
|
||||
- admin 端**无独立的 wa_users 查询 API**,需新增或复用 `consignment.js` 中的 `selfBonusLogListApi` 辅助拼合
|
||||
- 需删除的高级筛选项:等级、分组、标签、国家/省份、消费情况、访问情况、性别、身份(共 8 个筛选项)
|
||||
|
||||
---
|
||||
|
||||
## D 组:用户积分明细子页面测试
|
||||
|
||||
> 参考文件:`src/views/user/integral/index.vue`(241 行)
|
||||
|
||||
| 编号 | 测试场景 | 结果 | 详情 |
|
||||
|------|---------|------|------|
|
||||
| D-01 | 带 uid 参数加载 | ⚠️ **PRE-CHECK** | 原页面 `searchForm.uid` 已存在,只需在 `mounted()` 从 `$route.query.uid` 注入即可 |
|
||||
| D-02 | 概览卡片数据验证 | ⚠️ **PRE-CHECK** | 积分来自 `eb_user.integral` ✅;个人奖金来自 `wa_users.selfBonus`(admin 端无现成 API)⚠️ |
|
||||
| D-03 | 无 uid 参数访问 | ⚠️ **PRE-CHECK** | 原页面无 uid 校验逻辑,需在 `mounted()` 添加 fallback 处理 |
|
||||
| D-04 | 无效 uid 访问 | ⚠️ **PRE-CHECK** | 后端返回空列表即可,前端需处理空状态显示 |
|
||||
| D-05 | 时间范围筛选 | ✅ **PRE-PASS** | 原页面已有完整 `DateRangePicker` 实现,直接复用 |
|
||||
| D-06 | 积分变动显示 | ✅ **PRE-PASS** | 原页面已实现 `type===1` 绿色 `+`、否则红色 `-` 逻辑(第 65-66 行) |
|
||||
| D-07 | 状态与关联类型 | ✅ **PRE-PASS** | `linkTypeFilter` / `statusFilter` / `statusTypeFilter` 三个方法完整(第 196-223 行) |
|
||||
| D-08 | 返回按钮 | ⚠️ **PRE-CHECK** | 原页面无返回按钮,需手动添加 |
|
||||
| D-09 | 分页功能 | ✅ **PRE-PASS** | `[15, 30, 45, 60]` 分页完整实现,直接复用 |
|
||||
|
||||
**D 组结论**:参考页面仅 241 行,复用度最高(5/9 项可直接复用),是三个页面中风险最低的。
|
||||
|
||||
---
|
||||
|
||||
## E 组:接口与后端认证测试
|
||||
|
||||
| 编号 | 测试场景 | 结果 | 详情 |
|
||||
|------|---------|------|------|
|
||||
| E-01 | 免认证接口可达性 | ❌ **FAIL** | `UserIntegralController.getList()` 有 `@PreAuthorize("hasAuthority('admin:user:integral:list')")`,无 token 必返回 401 |
|
||||
| E-02 | 原认证接口不受影响 | ✅ **PASS** | 原接口认证逻辑未变动 |
|
||||
| E-03 | 接口仅读不写 | ✅ **PASS** | 积分 list 接口为 POST 查询,无写操作 |
|
||||
| E-04 | 大数据量分页 | ⏭️ **SKIP** | 待联调时测试 |
|
||||
| E-05 | 边界参数 | ⏭️ **SKIP** | 待联调时测试 |
|
||||
| E-06 | 数据脱敏验证 | ❌ **FAIL** | 当前 admin 接口无脱敏处理,用户手机号明文返回 |
|
||||
|
||||
**E 组关键发现**:
|
||||
- 后端 `WebSecurityConfig` 的 `permitAll` 白名单**不包含** `/api/admin/user/integral/**`
|
||||
- 需后端在 `WebSecurityConfig` 第 121 行附近新增:
|
||||
```java
|
||||
.antMatchers("/api/admin/user/integral/list").permitAll()
|
||||
```
|
||||
或新建 `ExternalIntegralController` 映射至免认证路径
|
||||
|
||||
---
|
||||
|
||||
## F 组:兼容性与 UI 测试
|
||||
|
||||
| 编号 | 测试场景 | 结果 |
|
||||
|------|---------|------|
|
||||
| F-01 ~ F-07 | 全部兼容性测试 | ⏭️ **SKIP** — 页面未创建,待开发完成后执行 |
|
||||
|
||||
---
|
||||
|
||||
## 问题汇总(需在开发中修复)
|
||||
|
||||
| 优先级 | 问题 | 影响范围 | 解决方案 |
|
||||
|--------|------|---------|---------|
|
||||
| 🔴 P0 | `permission.js` 白名单未更新 | A 组全部 FAIL | 修改白名单为前缀匹配 |
|
||||
| 🔴 P0 | 后端积分接口有 `@PreAuthorize` 强认证 | E-01 FAIL | 后端新增免认证路径或 controller |
|
||||
| 🟠 P1 | admin 端无独立 wa_users 查询 API | C-04、D-02 阻塞 | 复用寄卖模块的 `selfBonusLogListApi` 或后端新增聚合接口 |
|
||||
| 🟠 P1 | 用户手机号无脱敏处理 | E-06 FAIL | 后端接口或前端 filter 处理 `138****8888` |
|
||||
| 🟡 P2 | 原订单页 11 处权限指令需清理 | B-08 | 开发时逐一删除 |
|
||||
| 🟡 P2 | 原用户列表页 15 处权限指令需清理 | C-08 | 开发时逐一删除 |
|
||||
| 🟡 P2 | 积分明细页缺少 uid 空值校验和返回按钮 | D-03、D-08 | 开发时添加 |
|
||||
|
||||
---
|
||||
|
||||
## 测试覆盖统计
|
||||
|
||||
| 组别 | 总用例 | PASS | FAIL | PRE-CHECK | SKIP |
|
||||
|------|--------|------|------|-----------|------|
|
||||
| A 组(免登录) | 5 | 1 | 3 | 0 | 1 |
|
||||
| B 组(订单页) | 8 | 0 | 0 | 1 | 7 |
|
||||
| C 组(用户积分页) | 8 | 0 | 0 | 3 | 5 |
|
||||
| D 组(积分明细页) | 9 | 4 | 0 | 5 | 0 |
|
||||
| E 组(接口) | 6 | 2 | 2 | 0 | 2 |
|
||||
| F 组(兼容性) | 7 | 0 | 0 | 0 | 7 |
|
||||
| **合计** | **43** | **7** | **5** | **9** | **22** |
|
||||
|
||||
> PASS = 代码层面已满足条件;FAIL = 存在明确问题需修复;PRE-CHECK = 有条件可实现,开发时需注意;SKIP = 页面未创建,待开发完成后执行
|
||||
|
||||
---
|
||||
|
||||
*报告生成时间:2026-03-30*
|
||||
@@ -1,37 +0,0 @@
|
||||
# 管理后台中积分模块新增如下页面
|
||||
|
||||
## 积分订单页面
|
||||
- **已修复**新建页面,参考原页面:/order/index
|
||||
|
||||
## 用户积分页面
|
||||
- **已修复**新建页面,参考原页面:/user/index,增加wa_users的相关字段
|
||||
|
||||
### 用户积分明细子页面
|
||||
- **已修复**一个新建积分明细页面,参考原页面:“user/index 用户管理-》账户详情-》积分明细”,延用原后端api:/marketing/integral/integrallog
|
||||
|
||||
|
||||
## 修改
|
||||
|
||||
### 积分订单页面修改,路径:/integral-external/order
|
||||
|
||||
- **已修复**1. 去除订单类型的选择,默认入参:“普通订单”类型
|
||||
- **已修复**2. 订单列表中增加“使用积分”列
|
||||
- **已修复**3. 订单列表中增加用户信息相关列
|
||||
|
||||
|
||||
### 用户积分页面修改,路径:/integral-external/user
|
||||
|
||||
- **已修复**列表中个人奖金没有显示出来数据(从wa_users表中获取数据)
|
||||
|
||||
|
||||
### 用户积分明细页面修改,路径:/integral-external/user/integral-detail
|
||||
|
||||
- **已修复**增加用户id,用户名称,用户手机号输入框,作为查询接口参数
|
||||
- **已修复**没有用户id的时候,显示所有的积分明细数据,按照id或者时间倒序
|
||||
- **已修复**关联类型显示中文(含 order/sign/system/selfbonus 及未知值「其他(原值)」)
|
||||
|
||||
|
||||
|
||||
## 备注
|
||||
- **已修复**所有新建页面跳过用户登陆状态验证
|
||||
- **已修复**按照后端api最小修改原则,尽量延用原后端api
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,946 +0,0 @@
|
||||
---
|
||||
name: Agent Configuration v3s
|
||||
overview: 基于本机实际 OpenClaw 环境检查结果修正的配置方案。在现有 1 个 main Agent + 1 个飞书应用的基础上,增量添加 1 个积分商城 PM + 3 个通用开发 Agent,不影响已有配置。
|
||||
todos:
|
||||
- id: create-feishu-apps
|
||||
content: 在飞书开放平台创建 4 个机器人应用(或复用现有应用做路由)
|
||||
status: pending
|
||||
- id: update-openclaw-json
|
||||
content: 在现有 openclaw.json 中追加 4 个 Agent、bindings 和飞书账号
|
||||
status: pending
|
||||
- id: create-workspaces
|
||||
content: 创建 4 个 Agent workspace 目录和全套 .md 文件
|
||||
status: pending
|
||||
- id: install-skills
|
||||
content: 安装本地 Skills 和 ClawHub Skills
|
||||
status: pending
|
||||
- id: register-and-verify
|
||||
content: 运行 openclaw doctor 验证配置
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# OpenClaw 多 Agent 配置方案 v3s -- 1 PM + 3 通用开发
|
||||
|
||||
> **v3s 核心变更(相对 v3):**
|
||||
>
|
||||
> 1. 后端/前端/QA 三个 Agent 从"积分商城专用"改为**通用软件开发工程师**,可服务于任何项目
|
||||
> 2. 仅 PM 保留为积分商城专属项目经理
|
||||
> 3. Agent ID 重命名:`integral-backend/frontend/qa` → `dev-backend/frontend/qa`
|
||||
> 4. 项目路径确认为 `/Users/mac/scott-macair-26/integral-shop`
|
||||
> 5. 通用开发 Agent 的 SOUL.md 移除特定技术栈锁定,改为"按项目要求适配"
|
||||
|
||||
---
|
||||
|
||||
## 一、实际环境概况
|
||||
|
||||
### 1.1 本机 OpenClaw 配置(不可变动)
|
||||
|
||||
- **运行环境:** macOS,OpenClaw 2026.3.13
|
||||
- **配置文件:** `/Users/mac/.openclaw/openclaw.json`
|
||||
- **已有 Agent:** 仅 1 个(main)
|
||||
- **已有飞书:** 1 个应用(`cli_a930893990799cba`),websocket 连接,1 条 binding(main → default)
|
||||
- **模型 provider:** moonshot(Kimi K2.5)+ kimi-coding(k2p5),共用同一 API key
|
||||
- **默认模型:** `kimi-coding/k2p5`
|
||||
- **Gateway:** 端口 18789,local 模式,token 鉴权
|
||||
- **本地 Skills:** 0 个(仅飞书插件自带 feishu-doc/drive/perm/wiki)
|
||||
- **Workspace:** 1 个共享 workspace,默认模板状态
|
||||
|
||||
### 1.2 积分商城项目信息
|
||||
|
||||
- **项目路径:** `/Users/mac/scott-macair-26/integral-shop`
|
||||
- **Gitea:** `http://49.235.131.69:3000/scottpan/integral-shop.git`
|
||||
- **子项目:**
|
||||
- `backend/` → Java Spring Boot 后端(Java 1.8 / Spring Boot 2.2.6 / MyBatis Plus 3.3.1 / MySQL 5.7)
|
||||
- `backend-adminend/` → 管理后台 Vue 前端(Vue 2.6 / Element UI 2.13)
|
||||
- `single_uniapp22miao/` → 用户端 uni-app H5(Vue 3 / uni-app)
|
||||
|
||||
---
|
||||
|
||||
## 二、Agent 角色设计(1 专用 PM + 3 通用开发)
|
||||
|
||||
**设计理念:** PM 是项目专属的(绑定积分商城的需求、PRD、部署流程),但开发能力是通用的。3 个开发 Agent 可以同时服务于积分商城和未来的其他项目,PM 通过任务分派告诉它们具体的项目上下文。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
User[用户/飞书] -->|积分商城需求| PM["integral-pm (积分商城 PM)"]
|
||||
User -->|其他项目/通用编码任务| BE["dev-backend (通用后端)"]
|
||||
User -->|其他项目/通用编码任务| FE["dev-frontend (通用前端)"]
|
||||
User -->|其他项目/通用编码任务| QA["dev-qa (通用测试)"]
|
||||
PM -->|后端任务 + 项目上下文| BE
|
||||
PM -->|前端任务 + 项目上下文| FE
|
||||
PM -->|测试计划 + 项目上下文| QA
|
||||
BE -->|API 就绪| FE
|
||||
BE -->|提测| QA
|
||||
FE -->|提测| QA
|
||||
QA -->|Bug 反馈| BE
|
||||
QA -->|Bug 反馈| FE
|
||||
QA -->|测试报告| PM
|
||||
QA -.->|部署申请| PM
|
||||
PM -.->|部署审批| QA
|
||||
```
|
||||
|
||||
| Agent ID | 角色 | 职责范围 |
|
||||
| ----------------- | ------------ | --------------------------------------- |
|
||||
| **integral-pm** | 积分商城项目经理 + 设计 | 积分商城需求拆解、PRD、UI 规范、任务分派、进度跟踪、部署审批 |
|
||||
| **dev-backend** | 通用后端开发工程师 | 任意项目的后端开发(Java/Python/Go/Node 等,按项目要求适配) |
|
||||
| **dev-frontend** | 通用前端开发工程师 | 任意项目的前端开发(Vue/React/uni-app 等,按项目要求适配) |
|
||||
| **dev-qa** | 通用测试工程师 | 任意项目的功能测试、接口测试、UI 测试、部署执行 |
|
||||
|
||||
---
|
||||
|
||||
## 三、Agent 间通信协议
|
||||
|
||||
### 3.1 通信方式
|
||||
|
||||
采用**独立飞书应用方案**(每个 Agent 一个飞书机器人),通过 accountId 路由。
|
||||
|
||||
用户可以直接私聊任何开发 Agent 下达通用编码任务;积分商城相关任务则通过 PM 分派。
|
||||
|
||||
### 3.2 消息协议格式
|
||||
|
||||
PM 分派任务时必须携带项目上下文:
|
||||
|
||||
```
|
||||
【任务分派】<标题>
|
||||
发送方: integral-pm
|
||||
接收方: @<dev-agent>
|
||||
关联任务: <task-id>
|
||||
项目: 积分商城
|
||||
项目路径: /Users/mac/scott-macair-26/integral-shop
|
||||
---
|
||||
<任务描述>
|
||||
<技术栈约束>(如有)
|
||||
<验收标准>
|
||||
```
|
||||
|
||||
开发 Agent 之间、开发与 PM 之间的其他消息类型:任务分派、API-就绪、提测通知、Bug-反馈、测试报告、部署申请、部署审批、进度更新。
|
||||
|
||||
### 3.3 任务状态机
|
||||
|
||||
```
|
||||
Created → InProgress → CodeReview → Testing → Passed → DeployApproval → Deploying → Done
|
||||
↓ ↓
|
||||
BugFound ← ─ ─ ─ ─ ─ ─ ─ ┘
|
||||
↓
|
||||
InProgress(修复后重新流转)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、openclaw.json 增量修改
|
||||
|
||||
**原则:只追加,不修改已有配置。**
|
||||
|
||||
### 4.1 在 `agents` 中新增 `list` 字段
|
||||
|
||||
当前 `agents` 节点只有 `defaults`,需新增 `list`:
|
||||
|
||||
```json
|
||||
"agents": {
|
||||
"defaults": {
|
||||
... // 保持不变
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"id": "integral-pm",
|
||||
"name": "integral-pm",
|
||||
"workspace": "/Users/mac/.openclaw/workspace-integral-pm",
|
||||
"agentDir": "/Users/mac/.openclaw/agents/integral-pm/agent",
|
||||
"model": "kimi-coding/k2p5"
|
||||
},
|
||||
{
|
||||
"id": "dev-backend",
|
||||
"name": "dev-backend",
|
||||
"workspace": "/Users/mac/.openclaw/workspace-dev-backend",
|
||||
"agentDir": "/Users/mac/.openclaw/agents/dev-backend/agent",
|
||||
"model": "kimi-coding/k2p5"
|
||||
},
|
||||
{
|
||||
"id": "dev-frontend",
|
||||
"name": "dev-frontend",
|
||||
"workspace": "/Users/mac/.openclaw/workspace-dev-frontend",
|
||||
"agentDir": "/Users/mac/.openclaw/agents/dev-frontend/agent",
|
||||
"model": "kimi-coding/k2p5"
|
||||
},
|
||||
{
|
||||
"id": "dev-qa",
|
||||
"name": "dev-qa",
|
||||
"workspace": "/Users/mac/.openclaw/workspace-dev-qa",
|
||||
"agentDir": "/Users/mac/.openclaw/agents/dev-qa/agent",
|
||||
"model": "kimi-coding/k2p5"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 在 `bindings` 数组中追加 4 条飞书路由
|
||||
|
||||
```json
|
||||
"bindings": [
|
||||
{
|
||||
"agentId": "main",
|
||||
"match": { "channel": "feishu", "accountId": "default" }
|
||||
},
|
||||
{
|
||||
"agentId": "integral-pm",
|
||||
"match": { "channel": "feishu", "accountId": "jfshop@macair26" }
|
||||
},
|
||||
{
|
||||
"agentId": "dev-backend",
|
||||
"match": { "channel": "feishu", "accountId": "dev-backend@macair" }
|
||||
},
|
||||
{
|
||||
"agentId": "dev-frontend",
|
||||
"match": { "channel": "feishu", "accountId": "dev-frontend@macair" }
|
||||
},
|
||||
{
|
||||
"agentId": "dev-qa",
|
||||
"match": { "channel": "feishu", "accountId": "dev-qa@macair" }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4.3 在 `channels.feishu` 中追加 `accounts`
|
||||
|
||||
```json
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"enabled": true,
|
||||
"appId": "cli_a930893990799cba",
|
||||
"appSecret": "FfpFz93MKBx0ytC1ceTPF0BnjM7vFVhQ",
|
||||
"connectionMode": "websocket",
|
||||
"domain": "feishu",
|
||||
"groupPolicy": "open",
|
||||
"dmPolicy": "open",
|
||||
"allowFrom": ["*"],
|
||||
"accounts": {
|
||||
"jfshop@macair26": {
|
||||
"appId": "cli_a930893990799cba",
|
||||
"appSecret": "FfpFz93MKBx0ytC1ceTPF0BnjM7vFVhQ",
|
||||
"agent": "integral-pm",
|
||||
"dmPolicy": "open",
|
||||
"allowFrom": ["*"]
|
||||
},
|
||||
"dev-backend@macair": {
|
||||
"appId": "cli_a9316e2a92385bc7",
|
||||
"appSecret": "t7YyQU1qgqJFiW95HfA1SgnUBdlpx0F1",
|
||||
"agent": "dev-backend",
|
||||
"dmPolicy": "open",
|
||||
"allowFrom": ["*"]
|
||||
},
|
||||
"dev-frontend@macair": {
|
||||
"appId": "cli_a9316ef6f5785bb6",
|
||||
"appSecret": "dhJ3uAKWtZDzXce25YJ2HXHhw32eBGFR",
|
||||
"agent": "dev-frontend",
|
||||
"dmPolicy": "open",
|
||||
"allowFrom": ["*"]
|
||||
},
|
||||
"dev-qa@macair": {
|
||||
"appId": "cli_a9316f026ebadbc8",
|
||||
"appSecret": "PHN6UZgU21NGMCW5C6boQckDMFo228un",
|
||||
"agent": "dev-qa",
|
||||
"dmPolicy": "open",
|
||||
"allowFrom": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 不变动的部分
|
||||
|
||||
`meta`、`wizard`、`auth`、`models`、`tools`、`commands`、`session`、`gateway`、`plugins`、main Agent 的 binding 全部保持不变。
|
||||
|
||||
---
|
||||
|
||||
## 五、双模型架构
|
||||
|
||||
| 层 | 用途 | Agent | 模型 |
|
||||
| -------- | --------- | ---------------------- | ------------------------------- |
|
||||
| OpenClaw | 飞书对话、任务协调 | 全部 | kimi-coding/k2p5(已有) |
|
||||
| Cursor | 代码编写 | integral-pm | `agent --model claude-4.6-opus` |
|
||||
| Cursor | 代码编写 | dev-backend/frontend/qa | `agent --model auto` |
|
||||
|
||||
---
|
||||
|
||||
## 六、Skills 配置
|
||||
|
||||
### 6.1 阶段一:最小启动集(Day 1)
|
||||
|
||||
仅使用 OpenClaw 内置 Tools:
|
||||
|
||||
| 内置 Tool | integral-pm | dev-backend | dev-frontend | dev-qa |
|
||||
| ------------ | :---------: | :---------: | :----------: | :----: |
|
||||
| git | ● | ● | ● | ● |
|
||||
| file-manager | ● | ● | ● | ● |
|
||||
| web-search | ● | ● | ● | ● |
|
||||
| browser | ● | - | ● | ● |
|
||||
| code-runner | - | ● | ● | ● |
|
||||
| http-request | - | ● | - | ● |
|
||||
| **合计** | **4** | **5** | **5** | **6** |
|
||||
|
||||
### 6.2 阶段二:核心 Skills(Day 2-3)
|
||||
|
||||
```bash
|
||||
# 搜索 ClawHub 可用 Skill
|
||||
openclaw skills search gitea
|
||||
openclaw skills search cursor
|
||||
openclaw skills search code-review
|
||||
```
|
||||
|
||||
按搜索结果安装 cursor-cli、gitea-tools 等。
|
||||
|
||||
### 6.3 阶段三:按需引入(Week 2+)
|
||||
|
||||
代码审查、自动化测试、摘要等。
|
||||
|
||||
---
|
||||
|
||||
## 七、各 Agent Workspace 配置
|
||||
|
||||
---
|
||||
|
||||
### 1. PM Agent (integral-pm) — 积分商城专属
|
||||
|
||||
**workspace 路径:** `/Users/mac/.openclaw/workspace-integral-pm/`
|
||||
|
||||
**IDENTITY.md:**
|
||||
|
||||
```markdown
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** 积分商城PM
|
||||
- **Creature:** AI 项目经理
|
||||
- **Vibe:** 结构化、专业、高效
|
||||
- **Emoji:** 📋
|
||||
```
|
||||
|
||||
**SOUL.md:**
|
||||
|
||||
```markdown
|
||||
# SOUL.md - 积分商城 PM
|
||||
|
||||
## 角色定义
|
||||
积分商城项目的专属项目经理兼 UI 设计指导。
|
||||
负责积分商城的需求拆解、任务分派、进度跟踪、部署审批。
|
||||
|
||||
## 管辖项目
|
||||
- 项目名称: 单商户积分商城
|
||||
- 项目路径: /Users/mac/scott-macair-26/integral-shop
|
||||
- Gitea: http://49.235.131.69:3000/scottpan/integral-shop.git
|
||||
|
||||
## 下属 Agent
|
||||
- dev-backend: 通用后端开发(分派任务时须附带项目上下文和技术栈约束)
|
||||
- dev-frontend: 通用前端开发(同上)
|
||||
- dev-qa: 通用测试工程师(同上)
|
||||
|
||||
## 沟通风格
|
||||
- 结构化、简洁、中文为主
|
||||
- 任务分派必须使用标准消息协议,且包含项目路径和技术栈约束
|
||||
- 不说废话,直接给结论和下一步行动
|
||||
|
||||
## 决策原则
|
||||
- MVP 优先、增量迭代
|
||||
- 技术方案交由开发 Agent 决定,PM 不干预实现细节
|
||||
- 部署审批必须确认:测试通过率 ≥ 95%、无 P0 Bug
|
||||
|
||||
## 设计输出
|
||||
以文字描述 + 参考截图形式交付 UI 规范。
|
||||
管理后台遵循 Element UI 2.13 风格,用户端遵循现有积分商城 H5 风格。
|
||||
|
||||
## 积分商城技术栈约束(分派任务时传递给开发 Agent)
|
||||
### 后端
|
||||
- Java 1.8(禁止 Java 9+)、Spring Boot 2.2.6(禁止 3.x)、MyBatis Plus 3.3.1、MySQL 5.7(禁止 8.0 特性)、Maven 3.6.1、Redis 5.x
|
||||
|
||||
### 管理后台前端 (backend-adminend/)
|
||||
- Vue 2.6(禁止 Vue 3)、Element UI 2.13(禁止 Element Plus)、Vuex 3.x(禁止 Pinia)
|
||||
|
||||
### 用户端 H5 (single_uniapp22miao/)
|
||||
- uni-app + Vue 3、微信小程序兼容
|
||||
|
||||
## 禁止行为
|
||||
- 禁止自行修改本文件(SOUL.md)或 AGENTS.md
|
||||
```
|
||||
|
||||
**AGENTS.md:**
|
||||
|
||||
```markdown
|
||||
# AGENTS.md - PM 工作规范
|
||||
|
||||
## Session Startup
|
||||
1. Read SOUL.md
|
||||
2. Read USER.md
|
||||
3. Read memory/YYYY-MM-DD.md(今天 + 昨天)
|
||||
4. Read plans/ 下最新的 PRD
|
||||
|
||||
## 工作流
|
||||
1. 收到需求 → 写 PRD 到 plans/<feature>.md
|
||||
2. 拆解为子任务 → 写入 tasks/<YYYY-MM-DD>-<feature>-<subtask>.md
|
||||
3. 通过飞书分别通知 dev-backend / dev-frontend / dev-qa
|
||||
**重要:** 分派任务时必须附带以下项目上下文:
|
||||
- 项目路径: /Users/mac/scott-macair-26/integral-shop
|
||||
- 涉及的子项目: backend/ 或 backend-adminend/ 或 single_uniapp22miao/
|
||||
- 技术栈约束(从 SOUL.md 的"积分商城技术栈约束"部分复制)
|
||||
- Git 分支规范和 Gitea 地址
|
||||
|
||||
## 任务分派模板
|
||||
```
|
||||
【任务分派】<标题>
|
||||
发送方: integral-pm
|
||||
接收方: @<dev-agent>
|
||||
关联任务: <task-id>
|
||||
项目: 积分商城
|
||||
项目路径: /Users/mac/scott-macair-26/integral-shop
|
||||
子项目: <backend | backend-adminend | single_uniapp22miao>
|
||||
Gitea: http://49.235.131.69:3000/scottpan/integral-shop.git
|
||||
分支规范: feature/<role>-<name>
|
||||
---
|
||||
## 需求描述
|
||||
<需求正文>
|
||||
|
||||
## 技术栈约束
|
||||
<从 SOUL.md 复制对应子项目的技术栈约束>
|
||||
|
||||
## 验收标准
|
||||
<AC 列表>
|
||||
```
|
||||
|
||||
## 部署审批流程
|
||||
1. 收到 dev-qa 的【部署申请】
|
||||
2. 检查:测试报告通过率 ≥ 95%、无 P0 Bug
|
||||
3. 测试环境(by80)/ 预发布环境(miao33): 直接批准
|
||||
4. 生产环境(miao50): 需 @用户 人工确认后批准
|
||||
5. 回复【部署审批】消息
|
||||
|
||||
## Cursor 使用
|
||||
- agent --model claude-4.6-opus
|
||||
- 用途: 需求分析、代码审阅、架构设计
|
||||
|
||||
## Memory
|
||||
- 每日进度汇总到 memory/YYYY-MM-DD.md
|
||||
```
|
||||
|
||||
**TOOLS.md:**
|
||||
|
||||
```markdown
|
||||
# TOOLS.md - PM 环境信息
|
||||
|
||||
## 积分商城项目
|
||||
- 源码路径: /Users/mac/scott-macair-26/integral-shop
|
||||
- Gitea: http://49.235.131.69:3000/scottpan/integral-shop.git
|
||||
- 编码工具: Cursor IDE (macOS)
|
||||
|
||||
## 子项目结构
|
||||
- backend/ → Java Spring Boot 后端
|
||||
- backend-adminend/ → 管理后台 Vue 前端
|
||||
- single_uniapp22miao/ → 用户端 uni-app H5
|
||||
|
||||
## SSH 部署环境
|
||||
- 部署脚本: backend/shell/deploy-admin-*.sh, deploy-front-*.sh
|
||||
- 部署配置: backend/deploy.conf
|
||||
- 环境分级:
|
||||
- by80: 测试环境(PM 审批)
|
||||
- miao33: 预发布环境(PM 审批)
|
||||
- miao50: 生产环境(PM 审批 + 用户确认)
|
||||
- Admin JAR 远程端口: 30032
|
||||
- Front JAR 远程端口: 30031
|
||||
|
||||
## Cursor CLI
|
||||
- 模型: agent --model claude-4.6-opus
|
||||
- 项目目录: /Users/mac/scott-macair-26/integral-shop
|
||||
|
||||
## 已启用 Tools
|
||||
- 内置: git, file-manager, web-search, browser
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 通用后端开发 (dev-backend)
|
||||
|
||||
**workspace 路径:** `/Users/mac/.openclaw/workspace-dev-backend/`
|
||||
|
||||
**IDENTITY.md:**
|
||||
|
||||
```markdown
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** 后端开发
|
||||
- **Creature:** AI 后端工程师
|
||||
- **Vibe:** 技术精确、严谨、适应力强
|
||||
- **Emoji:** ⚙️
|
||||
```
|
||||
|
||||
**SOUL.md:**
|
||||
|
||||
```markdown
|
||||
# SOUL.md - 通用后端开发工程师
|
||||
|
||||
## 角色定义
|
||||
通用后端开发工程师。可服务于任何项目的后端开发工作,不绑定特定项目或技术栈。
|
||||
|
||||
## 核心能力
|
||||
- Java / Spring Boot / MyBatis 生态
|
||||
- Python / FastAPI / Django
|
||||
- Node.js / Express / Nest.js
|
||||
- Go 后端开发
|
||||
- 数据库设计与优化(MySQL / PostgreSQL / MongoDB / Redis)
|
||||
- RESTful API 和 GraphQL 设计
|
||||
- 微服务架构
|
||||
|
||||
## 工作原则
|
||||
- 接收任务时,严格遵守任务中指定的**技术栈版本约束**
|
||||
- 如果任务未指定版本,使用项目现有版本,不擅自升级
|
||||
- 接口变更须提供文档并说明影响范围
|
||||
- 代码编写在 Cursor IDE 中完成
|
||||
|
||||
## 沟通风格
|
||||
技术精确。变更通知包含:变更接口列表、请求/响应格式变化、影响的前端页面。
|
||||
|
||||
## 禁止行为
|
||||
- 禁止在未获得 PM 或用户明确批准的情况下引入新依赖
|
||||
- 禁止擅自修改项目配置文件中的端口、数据库连接等关键配置
|
||||
- 禁止自行修改本文件(SOUL.md)或 AGENTS.md
|
||||
```
|
||||
|
||||
**AGENTS.md:**
|
||||
|
||||
```markdown
|
||||
# AGENTS.md - 通用后端开发工作规范
|
||||
|
||||
## Session Startup
|
||||
1. Read SOUL.md
|
||||
2. Read USER.md
|
||||
3. Read memory/YYYY-MM-DD.md(今天 + 昨天)
|
||||
|
||||
## 接收任务方式
|
||||
1. **从 PM 接收**:PM 分派的任务包含项目路径、技术栈约束、验收标准,严格按要求执行
|
||||
2. **从用户直接接收**:用户可以直接私聊下达编码任务,按用户指示执行
|
||||
|
||||
## 通用开发流程
|
||||
1. 阅读任务描述,确认项目路径和技术栈约束
|
||||
2. 在对应项目目录中创建 feature/<role>-<name> 分支(或按任务指定的分支规范)
|
||||
3. 在 Cursor 中编码: agent --model auto
|
||||
4. 完成后通知前端(如有 API 变更)和 QA(提测)
|
||||
5. 使用任务指定的消息协议格式发送通知
|
||||
|
||||
## 故障恢复
|
||||
- Cursor CLI 失败: git stash → 记录 memory/errors.md → 通知 PM 或用户
|
||||
- 构建失败: 分析日志 → 尝试修复 → 3 次失败后上报
|
||||
|
||||
## Memory
|
||||
- 记录各项目的关键信息到 memory/ 下,方便后续会话恢复上下文
|
||||
```
|
||||
|
||||
**TOOLS.md:**
|
||||
|
||||
```markdown
|
||||
# TOOLS.md - 后端开发环境
|
||||
|
||||
## 本机环境 (macOS)
|
||||
- IDE: Cursor
|
||||
- 可用语言运行时: Java, Python, Node.js, Go(按项目需要)
|
||||
|
||||
## 已知项目
|
||||
|
||||
### 积分商城(由 integral-pm 管理)
|
||||
- 项目路径: /Users/mac/scott-macair-26/integral-shop/backend
|
||||
- 技术栈: Java 1.8 / Spring Boot 2.2.6 / MyBatis Plus 3.3.1 / MySQL 5.7 / Maven 3.6.1
|
||||
- 本地运行:
|
||||
- Admin API: mvn spring-boot:run -pl crmeb-admin (端口 8080)
|
||||
- Front API: mvn spring-boot:run -pl crmeb-front (端口 8081)
|
||||
- 打包:
|
||||
- Admin: mvn clean package -pl crmeb-admin -am -DskipTests
|
||||
- Front: mvn clean package -pl crmeb-front -am -DskipTests
|
||||
- 模块: crmeb-admin / crmeb-front / crmeb-service / crmeb-common
|
||||
- Gitea: http://49.235.131.69:3000/scottpan/integral-shop.git
|
||||
- 分支规范: feature/backend-<name>, bugfix/backend-<name>
|
||||
|
||||
(接手新项目时,在此追加项目信息)
|
||||
|
||||
## Cursor CLI
|
||||
- 模型: agent --model auto
|
||||
|
||||
## 已启用 Tools
|
||||
- 内置: git, file-manager, web-search, code-runner, http-request
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 通用前端开发 (dev-frontend)
|
||||
|
||||
**workspace 路径:** `/Users/mac/.openclaw/workspace-dev-frontend/`
|
||||
|
||||
**IDENTITY.md:**
|
||||
|
||||
```markdown
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** 前端开发
|
||||
- **Creature:** AI 前端工程师
|
||||
- **Vibe:** 创意、注重细节、灵活适配
|
||||
- **Emoji:** 🖥️
|
||||
```
|
||||
|
||||
**SOUL.md:**
|
||||
|
||||
```markdown
|
||||
# SOUL.md - 通用前端开发工程师
|
||||
|
||||
## 角色定义
|
||||
通用前端开发工程师。可服务于任何项目的前端开发工作,不绑定特定项目或技术栈。
|
||||
|
||||
## 核心能力
|
||||
- Vue 2.x / Vue 3.x 全家桶
|
||||
- React / Next.js
|
||||
- uni-app / 微信小程序
|
||||
- Element UI / Ant Design / Tailwind CSS
|
||||
- TypeScript
|
||||
- Webpack / Vite 构建工具
|
||||
- 响应式设计与跨端适配
|
||||
|
||||
## 工作原则
|
||||
- 接收任务时,严格遵守任务中指定的**技术栈版本约束**
|
||||
- **特别注意**:同一项目可能有多个前端子项目使用不同技术栈(如 Vue 2 管理后台 + Vue 3 用户端),切换时必须确认当前技术栈
|
||||
- 如果任务未指定版本,使用项目现有版本,不擅自升级
|
||||
- 代码编写在 Cursor IDE 中完成
|
||||
|
||||
## 沟通风格
|
||||
展示关键代码片段和页面效果说明。
|
||||
|
||||
## 禁止行为
|
||||
- 禁止在未获得 PM 或用户明确批准的情况下引入新 npm 依赖
|
||||
- 禁止在不同技术栈的子项目间共享组件(可能不兼容)
|
||||
- 禁止自行修改本文件(SOUL.md)或 AGENTS.md
|
||||
```
|
||||
|
||||
**AGENTS.md:**
|
||||
|
||||
```markdown
|
||||
# AGENTS.md - 通用前端开发工作规范
|
||||
|
||||
## Session Startup
|
||||
1. Read SOUL.md
|
||||
2. Read USER.md
|
||||
3. Read memory/YYYY-MM-DD.md(今天 + 昨天)
|
||||
|
||||
## 接收任务方式
|
||||
1. **从 PM 接收**:PM 分派的任务包含项目路径、子项目、技术栈约束
|
||||
2. **从用户直接接收**:用户可直接私聊下达编码任务
|
||||
|
||||
## 通用开发流程
|
||||
1. 阅读任务描述,确认项目路径、子项目和技术栈约束
|
||||
2. **关键步骤**:确认当前子项目的技术栈版本(避免 Vue 2 项目中写 Vue 3 代码)
|
||||
3. 创建 feature/frontend-<name> 分支
|
||||
4. 在 Cursor 中编码: agent --model auto
|
||||
5. 与后端协作获取 API 文档
|
||||
6. 完成后通知 QA 提测
|
||||
|
||||
## 故障恢复
|
||||
- 构建失败: 检查 Node 版本和 NODE_OPTIONS 环境变量
|
||||
- Cursor CLI 失败: git stash → 通知 PM 或用户
|
||||
```
|
||||
|
||||
**TOOLS.md:**
|
||||
|
||||
```markdown
|
||||
# TOOLS.md - 前端开发环境
|
||||
|
||||
## 本机环境 (macOS)
|
||||
- Node.js: 17+
|
||||
- IDE: Cursor
|
||||
|
||||
## 已知项目
|
||||
|
||||
### 积分商城(由 integral-pm 管理)
|
||||
|
||||
#### 管理后台 (backend-adminend/)
|
||||
- 路径: /Users/mac/scott-macair-26/integral-shop/backend-adminend
|
||||
- 技术栈: Vue 2.6 / Element UI 2.13 / Vuex 3.x / Vue Router 3.x
|
||||
- 开发: npm run dev(端口 9527)
|
||||
- 构建: npm run build:prod → dist/
|
||||
- 注意: Node 17+ 需 export NODE_OPTIONS="--openssl-legacy-provider"
|
||||
|
||||
#### 用户端 H5 (single_uniapp22miao/)
|
||||
- 路径: /Users/mac/scott-macair-26/integral-shop/single_uniapp22miao
|
||||
- 技术栈: uni-app + Vue 3、微信小程序兼容
|
||||
- 配置: config/app.js(API 基地址)
|
||||
- 开发: npm run dev:h5
|
||||
- 构建: npm run build:h5 → unpackage/dist/build/h5/
|
||||
|
||||
- Gitea: http://49.235.131.69:3000/scottpan/integral-shop.git
|
||||
- 分支规范: feature/frontend-<name>, bugfix/frontend-<name>
|
||||
|
||||
(接手新项目时,在此追加项目信息)
|
||||
|
||||
## Cursor CLI
|
||||
- 模型: agent --model auto
|
||||
|
||||
## 已启用 Tools
|
||||
- 内置: git, file-manager, web-search, code-runner, browser
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 通用测试工程师 (dev-qa)
|
||||
|
||||
**workspace 路径:** `/Users/mac/.openclaw/workspace-dev-qa/`
|
||||
|
||||
**IDENTITY.md:**
|
||||
|
||||
```markdown
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** 测试工程师
|
||||
- **Creature:** AI QA 工程师
|
||||
- **Vibe:** 严谨、细致、不放过任何 Bug
|
||||
- **Emoji:** 🧪
|
||||
```
|
||||
|
||||
**SOUL.md:**
|
||||
|
||||
```markdown
|
||||
# SOUL.md - 通用测试工程师
|
||||
|
||||
## 角色定义
|
||||
通用 QA 测试工程师 + 部署执行。可服务于任何项目的测试和部署工作。
|
||||
|
||||
## 核心能力
|
||||
- 功能测试、接口测试、UI 测试、回归测试
|
||||
- SSH 部署执行与验证
|
||||
- 测试用例编写
|
||||
- Bug 分析与根因定位(只读分析,不修改源码)
|
||||
|
||||
## 工作原则
|
||||
- 部署操作必须走 PM 审批流程(有 PM 管理的项目)
|
||||
- 用户直接下达的部署任务可直接执行
|
||||
- 生产环境部署始终需要用户人工确认
|
||||
|
||||
## Bug 描述规范
|
||||
1. 复现步骤(精确到操作路径)
|
||||
2. 期望结果
|
||||
3. 实际结果
|
||||
4. 截图/日志
|
||||
5. 影响范围评估(P0-P2)
|
||||
|
||||
## 禁止行为
|
||||
- 禁止修改源代码(只报 Bug,不自行修复)
|
||||
- 禁止自行修改本文件(SOUL.md)或 AGENTS.md
|
||||
```
|
||||
|
||||
**AGENTS.md:**
|
||||
|
||||
```markdown
|
||||
# AGENTS.md - 通用 QA 工作规范
|
||||
|
||||
## Session Startup
|
||||
1. Read SOUL.md
|
||||
2. Read USER.md
|
||||
3. Read memory/YYYY-MM-DD.md(今天 + 昨天)
|
||||
|
||||
## 接收任务方式
|
||||
1. **从 PM 接收**:PM 分派的任务包含项目上下文、测试范围
|
||||
2. **从用户/开发 Agent 接收**:提测通知或直接测试任务
|
||||
|
||||
## 通用测试流程
|
||||
1. 阅读任务描述和 API 文档
|
||||
2. 编写测试用例: tasks/test-<project>-<YYYY-MM-DD>-<feature>.md
|
||||
3. 执行测试:
|
||||
- 后端 API: http-request 工具调用接口
|
||||
- 前端 UI: browser 工具访问页面截图
|
||||
4. Bug 报告: tasks/bug-<project>-<YYYY-MM-DD>-<id>.md
|
||||
5. 测试通过 → 向 PM 发送测试报告
|
||||
|
||||
## 部署流程
|
||||
### 有 PM 管理的项目(如积分商城)
|
||||
1. 发送【部署申请】给 PM → 等待审批 → 执行部署 → 验证
|
||||
2. 生产环境需 PM 审批 + 用户确认
|
||||
|
||||
### 用户直接交办的部署
|
||||
1. 按用户指示执行,生产环境仍需用户确认
|
||||
|
||||
## 部署后验证
|
||||
- 健康检查、核心接口可用性、页面可访问性
|
||||
|
||||
## Cursor 使用
|
||||
- agent --model auto
|
||||
- 用途: 编写测试脚本、分析 Bug 根因(只读)
|
||||
```
|
||||
|
||||
**TOOLS.md:**
|
||||
|
||||
```markdown
|
||||
# TOOLS.md - QA 测试环境
|
||||
|
||||
## 本机环境 (macOS)
|
||||
- IDE: Cursor
|
||||
|
||||
## 已知项目
|
||||
|
||||
### 积分商城(由 integral-pm 管理)
|
||||
- 项目路径: /Users/mac/scott-macair-26/integral-shop
|
||||
- 本地服务:
|
||||
- 管理后台前端: http://localhost:9527
|
||||
- Admin API: http://localhost:8080
|
||||
- Front API: http://localhost:8081
|
||||
- SSH 部署:
|
||||
- 脚本: backend/shell/deploy-admin-*.sh, deploy-front-*.sh
|
||||
- 配置: backend/deploy.conf
|
||||
- 环境分级:
|
||||
- by80: 测试环境(PM 审批)
|
||||
- miao33: 预发布环境(PM 审批)
|
||||
- miao50: 生产环境(PM 审批 + 用户确认)
|
||||
- Admin JAR 端口: 30032
|
||||
- Front JAR 端口: 30031
|
||||
- Gitea: http://49.235.131.69:3000/scottpan/integral-shop.git
|
||||
|
||||
(接手新项目时,在此追加项目信息)
|
||||
|
||||
## Cursor CLI
|
||||
- 模型: agent --model auto
|
||||
|
||||
## 已启用 Tools
|
||||
- 内置: git, file-manager, web-search, code-runner, browser, http-request
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、Git 工作流(积分商城)
|
||||
|
||||
```
|
||||
main # 生产分支
|
||||
develop # 开发主分支
|
||||
feature/backend-<name> # 后端功能分支
|
||||
feature/frontend-<name> # 前端功能分支
|
||||
bugfix/backend-<name> # 后端修复分支
|
||||
bugfix/frontend-<name> # 前端修复分支
|
||||
release/<version> # 发布分支
|
||||
```
|
||||
|
||||
> 其他项目的 Git 工作流按各项目要求,由 PM 或用户在任务中指定。
|
||||
|
||||
---
|
||||
|
||||
## 九、初始化步骤
|
||||
|
||||
### 步骤 1:在飞书开放平台创建 4 个机器人应用
|
||||
|
||||
| 应用名称 | accountId | appId | 状态 |
|
||||
| --------- | ------------------ | ------------------------ | ---- |
|
||||
| 积分商城-PM | jfshop@macair26 | `cli_a930893990799cba` | ✅ 复用现有 |
|
||||
| 后端开发 | dev-backend@macair | `cli_a9316e2a92385bc7` | ✅ 已创建 |
|
||||
| 前端开发 | dev-frontend@macair| `cli_a9316ef6f5785bb6` | ✅ 已创建 |
|
||||
| 测试工程师 | dev-qa@macair | `cli_a9316f026ebadbc8` | ✅ 已创建 |
|
||||
|
||||
每个应用需启用:机器人能力、接收消息事件。连接模式使用 **websocket**。
|
||||
> 3 个 dev Agent 的飞书应用已创建完毕,仅 integral-pm 待创建。
|
||||
|
||||
### 步骤 2:备份当前配置
|
||||
|
||||
```bash
|
||||
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.before-agents
|
||||
```
|
||||
|
||||
### 步骤 3:创建目录
|
||||
|
||||
```bash
|
||||
# Workspace 目录
|
||||
mkdir -p ~/.openclaw/workspace-integral-pm/{memory,plans,tasks}
|
||||
mkdir -p ~/.openclaw/workspace-dev-{backend,frontend,qa}/{memory,tasks}
|
||||
|
||||
# Agent 目录
|
||||
mkdir -p ~/.openclaw/agents/integral-pm/agent
|
||||
mkdir -p ~/.openclaw/agents/dev-{backend,frontend,qa}/agent
|
||||
```
|
||||
|
||||
### 步骤 4:增量修改 openclaw.json
|
||||
|
||||
按第四节追加 `agents.list`、`bindings`、`channels.feishu.accounts`。
|
||||
**不删除或修改任何已有配置。**
|
||||
|
||||
### 步骤 5:写入 Workspace 文件
|
||||
|
||||
为每个 workspace 写入第七节中的 IDENTITY.md、SOUL.md、AGENTS.md、USER.md、TOOLS.md。
|
||||
|
||||
```bash
|
||||
for ws in integral-pm dev-backend dev-frontend dev-qa; do
|
||||
echo "# HEARTBEAT.md" > ~/.openclaw/workspace-$ws/HEARTBEAT.md
|
||||
done
|
||||
```
|
||||
|
||||
### 步骤 6:启用内置 Tools
|
||||
|
||||
```bash
|
||||
# 所有 Agent 通用
|
||||
for agent in integral-pm dev-backend dev-frontend dev-qa; do
|
||||
openclaw skills enable git --agent $agent
|
||||
openclaw skills enable file-manager --agent $agent
|
||||
openclaw skills enable web-search --agent $agent
|
||||
done
|
||||
|
||||
# 按角色差异化
|
||||
openclaw skills enable browser --agent integral-pm
|
||||
|
||||
openclaw skills enable code-runner --agent dev-backend
|
||||
openclaw skills enable http-request --agent dev-backend
|
||||
|
||||
openclaw skills enable code-runner --agent dev-frontend
|
||||
openclaw skills enable browser --agent dev-frontend
|
||||
|
||||
openclaw skills enable code-runner --agent dev-qa
|
||||
openclaw skills enable browser --agent dev-qa
|
||||
openclaw skills enable http-request --agent dev-qa
|
||||
```
|
||||
|
||||
### 步骤 7:验证
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw agents list
|
||||
openclaw agents list --bindings
|
||||
# 在飞书中向 main 机器人发消息确认不受影响
|
||||
# 分别向 4 个新机器人发消息确认路由正确
|
||||
```
|
||||
|
||||
### 回滚方案
|
||||
|
||||
```bash
|
||||
cp ~/.openclaw/openclaw.json.before-agents ~/.openclaw/openclaw.json
|
||||
openclaw restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、安全性约束
|
||||
|
||||
### 10.1 SSH 密钥
|
||||
- Workspace 文件中不记录 SSH 密钥路径
|
||||
- 部署脚本通过 deploy.conf 中的环境变量引用
|
||||
|
||||
### 10.2 环境分级(积分商城)
|
||||
|
||||
| 环境 | QA 直接操作 | PM 审批 | 用户确认 |
|
||||
| ------ | ------- | ----- | ---- |
|
||||
| by80 | ● | ● | - |
|
||||
| miao33 | ● | ● | - |
|
||||
| miao50 | - | ● | ● |
|
||||
|
||||
### 10.3 敏感信息
|
||||
- API key 仅存在 openclaw.json 和 agent/auth-profiles.json 中
|
||||
- 飞书 appSecret 仅存在 openclaw.json 中
|
||||
- Workspace .md 文件不记录任何密钥或密码
|
||||
|
||||
---
|
||||
|
||||
## 附录:v3 → v3s 变更总结
|
||||
|
||||
| 维度 | v3 | v3s |
|
||||
| -------------- | ---------------------------------- | -------------------------------------------- |
|
||||
| Agent 命名 | integral-backend/frontend/qa | dev-backend/frontend/qa(通用命名) |
|
||||
| 开发 Agent 定位 | 积分商城专用 | **通用软件开发**,可服务任何项目 |
|
||||
| SOUL.md 技术栈 | 写死特定版本约束 | 列出核心能力,按任务指定的约束执行 |
|
||||
| TOOLS.md 项目信息 | 只有积分商城 | "已知项目"区块,可追加新项目 |
|
||||
| PM 任务分派 | 直接下达 | 必须附带**项目路径 + 技术栈约束 + 分支规范** |
|
||||
| 用户直接使用开发 Agent | 不支持 | **支持**,用户可直接私聊开发 Agent 下达任何编码任务 |
|
||||
| workspace 目录命名 | workspace-integral-{role} | PM: workspace-integral-pm,其余: workspace-dev-{role} |
|
||||
| 项目路径 | `<PROJECT_ROOT>` 占位符 | `/Users/mac/scott-macair-26/integral-shop` |
|
||||
@@ -1,127 +0,0 @@
|
||||
# Phase 1 检查点报告 — 17:30 自动检查
|
||||
|
||||
> 生成时间:2026-03-30 17:30
|
||||
> 检查范围:`backend-adminend/src`
|
||||
|
||||
---
|
||||
|
||||
## 检查结果汇总
|
||||
|
||||
| # | 检查项 | 状态 | 说明 |
|
||||
|---|--------|------|------|
|
||||
| 1 | `EmptyLayout.vue` 空白布局 | ❌ **未找到** | `src/layout/` 目录下只有 `index.vue`,未创建 EmptyLayout |
|
||||
| 2 | `requestNoAuth.js` 免认证请求实例 | ❌ **未找到** | `src/utils/` 目录下只有 `request.js`,未创建 requestNoAuth |
|
||||
| 3 | 路由模块 `integralExternal.js` | ❌ **未找到** | `src/router/modules/` 下无此文件,constantRoutes 未注册 |
|
||||
| 4 | `permission.js` 白名单前缀匹配 | ❌ **未修改** | 当前仍为精确匹配:`whiteList.indexOf(to.path) !== -1`,未改为前缀匹配 |
|
||||
| 5 | API 文件 `integralExternal.js` | ❌ **未找到** | `src/api/` 目录下无此文件 |
|
||||
| 6 | 冒烟验证(无 token 访问不跳转登录) | ⚠️ **无法验证** | 基础设施文件均未创建,无法执行冒烟测试 |
|
||||
|
||||
---
|
||||
|
||||
## 当前实际状态
|
||||
|
||||
**Phase 1 全部 5 项任务均未完成。**
|
||||
|
||||
当前 `permission.js` 白名单内容:
|
||||
```js
|
||||
const whiteList = ['/login', '/auth-redirect'];
|
||||
// 匹配方式:whiteList.indexOf(to.path) !== -1(精确匹配)
|
||||
```
|
||||
|
||||
访问 `/integral-external/order` 无 token 时,**会被重定向到登录页**。
|
||||
|
||||
---
|
||||
|
||||
## 建议行动
|
||||
|
||||
### 立即按顺序创建以下文件:
|
||||
|
||||
**步骤 1:创建 `src/layout/EmptyLayout.vue`**
|
||||
```vue
|
||||
<template>
|
||||
<div class="empty-layout">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmptyLayout'
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**步骤 2:创建 `src/utils/requestNoAuth.js`**
|
||||
```js
|
||||
import axios from 'axios'
|
||||
|
||||
const requestNoAuth = axios.create({
|
||||
baseURL: process.env.VUE_APP_BASE_API,
|
||||
timeout: 15000
|
||||
})
|
||||
|
||||
requestNoAuth.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => Promise.reject(error)
|
||||
)
|
||||
|
||||
export default requestNoAuth
|
||||
```
|
||||
|
||||
**步骤 3:创建 `src/router/modules/integralExternal.js`**
|
||||
```js
|
||||
import EmptyLayout from '@/layout/EmptyLayout'
|
||||
|
||||
const integralExternalRouter = {
|
||||
path: '/integral-external',
|
||||
component: EmptyLayout,
|
||||
children: [
|
||||
{ path: 'order', name: 'IntegralOrder', component: () => import('@/views/integral/external/order/index') },
|
||||
{ path: 'user', name: 'IntegralUser', component: () => import('@/views/integral/external/user/index') },
|
||||
{ path: 'detail', name: 'IntegralDetail', component: () => import('@/views/integral/external/detail/index') }
|
||||
]
|
||||
}
|
||||
|
||||
export default integralExternalRouter
|
||||
```
|
||||
|
||||
**步骤 4:修改 `src/permission.js` 白名单为前缀匹配**
|
||||
```js
|
||||
// 改为:
|
||||
const whiteList = ['/login', '/auth-redirect', '/integral-external'];
|
||||
|
||||
// 修改匹配逻辑(约第 55 行):
|
||||
if (whiteList.some(path => to.path.startsWith(path))) {
|
||||
next();
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`);
|
||||
NProgress.done();
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 5:创建 `src/api/integralExternal.js`**(基础框架)
|
||||
```js
|
||||
import requestNoAuth from '@/utils/requestNoAuth'
|
||||
|
||||
export function getIntegralOrderList(params) {
|
||||
return requestNoAuth({ url: '/api/integral/order/list', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getIntegralUserList(params) {
|
||||
return requestNoAuth({ url: '/api/integral/user/list', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getIntegralDetail(params) {
|
||||
return requestNoAuth({ url: '/api/integral/detail/list', method: 'get', params })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要提示
|
||||
|
||||
**免登录链路是后续 Phase 2~4 一切工作的前提**,如果 permission.js 白名单不通,所有积分外部页面都无法访问。
|
||||
|
||||
请优先确保 `permission.js` 的前缀匹配逻辑正确生效后,再进入 Phase 2 开发。
|
||||
|
||||
当前时间已到 17:30,**建议立即开始 Phase 1 任务**,完成后方可进入 Phase 2:积分订单页面开发。
|
||||
@@ -1,89 +0,0 @@
|
||||
# Phase 4 检查点报告 — 18:50 自动检查
|
||||
|
||||
> 生成时间:2026-03-30 18:50
|
||||
> 检查范围:`backend-adminend/src`
|
||||
|
||||
---
|
||||
|
||||
## 检查结果汇总
|
||||
|
||||
| # | 检查项 | 状态 | 说明 |
|
||||
|---|--------|------|------|
|
||||
| 1 | 积分明细页面(从 `user/integral/index.vue` 复制并修改) | ❌ **未完成** | `views/integral/external/detail/` 目录不存在,未创建任何外部页面 |
|
||||
| 2 | URL query 参数 `uid` 自动注入搜索参数 | ❌ **未完成** | 外部积分明细页面未创建,无法验证 uid 参数读取 |
|
||||
| 3 | 顶部概览卡片(`eb_user.integral` + `wa_users.selfBonus`) | ❌ **未完成** | 无新增页面,概览卡片不存在 |
|
||||
| 4 | 返回按钮跳回用户积分列表 | ❌ **未完成** | 页面未创建 |
|
||||
| 5 | 分页和时间筛选 | ❌ **未完成** | 页面未创建 |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 根因分析
|
||||
|
||||
**Phase 4 的全部 5 项检查均未通过,根本原因是 Phase 1 基础设施仍未搭建。**
|
||||
|
||||
截至本次检查,以下前置依赖均不存在:
|
||||
|
||||
| 前置项 | 状态 |
|
||||
|--------|------|
|
||||
| `src/layout/EmptyLayout.vue` | ❌ 未创建 |
|
||||
| `src/utils/requestNoAuth.js` | ❌ 未创建 |
|
||||
| `src/router/modules/integralExternal.js` | ❌ 未创建 |
|
||||
| `src/api/integralExternal.js` | ❌ 未创建 |
|
||||
| `permission.js` 白名单前缀匹配改造 | ❌ 未修改 |
|
||||
| `router/index.js` 注册 constantRoutes | ❌ 未修改 |
|
||||
|
||||
Phase 1 → Phase 2 → Phase 3 → Phase 4 均为顺序依赖,无法跳过。
|
||||
|
||||
---
|
||||
|
||||
## 源文件就绪情况
|
||||
|
||||
积分明细源页面 `src/views/user/integral/index.vue` 存在(242 行),结构清晰:
|
||||
|
||||
- ✅ 已有 `searchForm.uid` 字段 — 可直接从 `$route.query.uid` 注入
|
||||
- ✅ 已有时间选择器 `daterange` — 分页和时间筛选逻辑可复用
|
||||
- ✅ 已有 `integralListApi` 数据请求 — 需替换为 `requestNoAuth` 版本
|
||||
- ⬜ 需新增:顶部概览卡片(调用用户详情接口获取 `integral` 和 `selfBonus`)
|
||||
- ⬜ 需新增:返回按钮(`this.$router.push('/integral-external/user')`)
|
||||
|
||||
改造量确实很小(~50 行修改),**确认源页面仅 242 行,风险最低**。
|
||||
|
||||
---
|
||||
|
||||
## 能否进入 Phase 5?
|
||||
|
||||
**❌ 不能进入 Phase 5(联调验证 + 提交)。**
|
||||
|
||||
Phase 5 的前提是 Phase 1~4 全部完成。当前连 Phase 1 都未完成。
|
||||
|
||||
---
|
||||
|
||||
## 建议行动
|
||||
|
||||
### 方案 A:快速补救(推荐)
|
||||
|
||||
如果用户仍有时间,建议按以下**压缩顺序**一次性完成 Phase 1 + Phase 4:
|
||||
|
||||
1. **创建 `EmptyLayout.vue`**(1 分钟)
|
||||
2. **创建 `requestNoAuth.js`**(2 分钟)
|
||||
3. **修改 `permission.js` 白名单**(2 分钟)
|
||||
4. **创建路由模块 + 注册 constantRoutes**(3 分钟)
|
||||
5. **复制 `user/integral/index.vue` → 外部积分明细页面**(5 分钟)
|
||||
- 注入 `$route.query.uid`
|
||||
- 替换 API 为免认证版本
|
||||
- 添加概览卡片和返回按钮
|
||||
6. **冒烟测试**(5 分钟)
|
||||
|
||||
预计总耗时:~18 分钟
|
||||
|
||||
### 方案 B:仅完成基础设施
|
||||
|
||||
如果时间紧张,优先完成 Phase 1 基础设施,确保免登录链路畅通,Phase 4 积分明细页面留到下次。
|
||||
|
||||
---
|
||||
|
||||
## 参考文档
|
||||
|
||||
- 开发计划:`docs/integral-pages-schedule.md`
|
||||
- 技术方案:`docs/integral-pages-coding-plan.md`
|
||||
- Phase 1 检查报告:`docs/phase1-checkpoint-report.md`(17:30 生成,全部未通过)
|
||||
55
docs/resell-change-bygsf212 copy.md
Executable file
55
docs/resell-change-bygsf212 copy.md
Executable file
@@ -0,0 +1,55 @@
|
||||
|
||||
# 新公司(宝应桂圣富商贸)项目的h5端(目录:h5) 修改任务
|
||||
|
||||
Ecs ip: 118.31.36.212
|
||||
|
||||
## **修改任务**
|
||||
|
||||
- **已完成**新建分支:bygsf212,合并byhlc112分支的最新代码到该分支,并根据上述信息修改相关需要变更项,使符合该新公司项目环境
|
||||
|
||||
## 配置项修改
|
||||
|
||||
1.新项目公司名称,eg:宝应桂圣富商贸,
|
||||
2.使用"宝应桂圣富商贸"的**HTML十进制实体**编码,修改wa_options表中name=system_config中的value值里的title信息,
|
||||
3.相关配置项:
|
||||
A. **寄卖商城API地址**:https://admin.b3y45.com/api
|
||||
B. **寄卖商城后台地址**:https://admin.b3y45.com
|
||||
C. **寄卖商城H5地址**:https://b3y45.com/
|
||||
D. **云服务器寄卖商城H5目录**:/www/wwwroot/b3y45.com
|
||||
E. **云服务器寄卖商城后台目录**:/www/wwwroot/admin.b3y45.com
|
||||
|
||||
F. **积分商城地址**:https://jf.b3y45.com
|
||||
G. **云服务器积分商城目录**:/www/wwwroot/jf.b3y45.com
|
||||
|
||||
4. 短信服务
|
||||
SMS_SIGNNAME = '宝应宏煜春商贸'
|
||||
SMS_TEMPLATE = 'SMS_334545236'
|
||||
SMS_KEYID = 'LTAI5t7mHU5L4ChxXQk4vw4T'
|
||||
SMS_KEYSECRET = 'X9yonEufGZJXEMtFXQvY5oJQmk0yno'
|
||||
|
||||
5. **webman.bin相关**
|
||||
sn_id: 17533260260610
|
||||
APP_SECRET: ZFyTNQTWEkCBczKzyUDJWE9Ecx260610
|
||||
|
||||
|
||||
### **寄卖商城H5**中需要修改的文件
|
||||
|
||||
A.修改h5/static/configs.js中如下内容
|
||||
TITLE: '宝应桂圣富商贸',
|
||||
BASE_URL: 'https://admin.b3y45.com/api',
|
||||
IMG_URL: 'https://admin.b3y45.com',
|
||||
H5_URL: 'https://b3y45.com',
|
||||
**sn_id**、**appStr**必须修改为webman.bin相关中的值
|
||||
|
||||
B. 修改h5/static/js/pages-personal-index.6f5415f9.js
|
||||
第270行:https://jf.b3y45.com/pages/integral/points?username=
|
||||
|
||||
C. 修改h5/static/js/pages-sub-pages-webview-index.1042489b.js
|
||||
第15行:https://jf.b3y45.com/?sn_id
|
||||
第43行:https://jf.b3y45.com/?user_id=
|
||||
|
||||
### **寄卖商城后端**项目中需要修改的文件
|
||||
|
||||
- 修改houtai/.env环境变量配置文件中的短信信息、APP_SECRET。
|
||||
- mysql数据库使用阿里云rds:rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
- rds中项目数据库名:bygsf212
|
||||
55
docs/resell-change-bygsf212.md
Executable file
55
docs/resell-change-bygsf212.md
Executable file
@@ -0,0 +1,55 @@
|
||||
|
||||
# 新公司(宝应桂圣富商贸)项目的h5端(目录:h5) 修改任务
|
||||
|
||||
Ecs ip: 118.31.36.212
|
||||
|
||||
## **修改任务**
|
||||
|
||||
- 新建分支:bygsf212,合并byhlc112分支的最新代码到该分支,并根据上述信息修改相关需要变更项,使符合该新公司项目环境
|
||||
|
||||
## 配置项修改
|
||||
|
||||
1.新项目公司名称,eg:宝应桂圣富商贸,
|
||||
2.使用"宝应桂圣富商贸"的**HTML十进制实体**编码,修改wa_options表中name=system_config中的value值里的title信息,
|
||||
3.相关配置项:
|
||||
A. **寄卖商城API地址**:https://admin.b3y45.com/api
|
||||
B. **寄卖商城后台地址**:https://admin.b3y45.com
|
||||
C. **寄卖商城H5地址**:https://b3y45.com/
|
||||
D. **云服务器寄卖商城H5目录**:/www/wwwroot/b3y45.com
|
||||
E. **云服务器寄卖商城后台目录**:/www/wwwroot/admin.b3y45.com
|
||||
|
||||
F. **积分商城地址**:https://jf.b3y45.com
|
||||
G. **云服务器积分商城目录**:/www/wwwroot/jf.b3y45.com
|
||||
|
||||
4. 短信服务
|
||||
SMS_SIGNNAME = '宝应宏煜春商贸'
|
||||
SMS_TEMPLATE = 'SMS_334545236'
|
||||
SMS_KEYID = 'LTAI5t7mHU5L4ChxXQk4vw4T'
|
||||
SMS_KEYSECRET = 'X9yonEufGZJXEMtFXQvY5oJQmk0yno'
|
||||
|
||||
5. **webman.bin相关**
|
||||
sn_id: 17533260260610
|
||||
APP_SECRET: ZFyTNQTWEkCBczKzyUDJWE9Ecx260610
|
||||
|
||||
|
||||
### **寄卖商城H5**中需要修改的文件
|
||||
|
||||
A.修改h5/static/configs.js中如下内容
|
||||
TITLE: '宝应桂圣富商贸',
|
||||
BASE_URL: 'https://admin.b3y45.com/api',
|
||||
IMG_URL: 'https://admin.b3y45.com',
|
||||
H5_URL: 'https://b3y45.com',
|
||||
**sn_id**、**appStr**必须修改为webman.bin相关中的值
|
||||
|
||||
B. 修改h5/static/js/pages-personal-index.6f5415f9.js
|
||||
第270行:https://jf.b3y45.com/pages/integral/points?username=
|
||||
|
||||
C. 修改h5/static/js/pages-sub-pages-webview-index.1042489b.js
|
||||
第15行:https://jf.b3y45.com/?sn_id
|
||||
第43行:https://jf.b3y45.com/?user_id=
|
||||
|
||||
### **寄卖商城后端**项目中需要修改的文件
|
||||
|
||||
- 修改houtai/.env环境变量配置文件中的短信信息、APP_SECRET。
|
||||
- mysql数据库使用阿里云rds:rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com
|
||||
- rds中项目数据库名:bygsf212
|
||||
50
docs/sql/com-czcf82-data-delete-0601-2.sql
Normal file
50
docs/sql/com-czcf82-data-delete-0601-2.sql
Normal file
@@ -0,0 +1,50 @@
|
||||
-- 池州春芳商贸二次数据清理
|
||||
-- 执行时间:2026-06-01 21:06:25
|
||||
-- 数据范围:92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251
|
||||
|
||||
START TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_users_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_users WHERE id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eb_user_bak_20260601_210625 AS
|
||||
SELECT * FROM eb_user WHERE uid IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_order_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_order
|
||||
WHERE seller_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251)
|
||||
OR buyer_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_merchandise_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_merchandise WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_selfbonus_log_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_selfbonus_log WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_sharebonus_log_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_sharebonus_log WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_coupon_log_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_coupon_log WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_withdraw_bak_20260601_210625 AS
|
||||
SELECT * FROM wa_withdraw WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eb_store_order_bak_20260601_210625 AS
|
||||
SELECT * FROM eb_store_order WHERE uid IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eb_user_integral_record_bak_20260601_210625 AS
|
||||
SELECT * FROM eb_user_integral_record WHERE uid IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
DELETE FROM wa_users WHERE id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM eb_user WHERE uid IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM wa_order WHERE seller_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251) OR buyer_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM wa_merchandise WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM wa_selfbonus_log WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM wa_sharebonus_log WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM wa_coupon_log WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM wa_withdraw WHERE user_id IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM eb_store_order WHERE uid IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
DELETE FROM eb_user_integral_record WHERE uid IN (92738,92827,93106,93119,93132,93133,93134,93140,93150,93156,93163,93166,93171,93172,93173,93182,93183,93185,93187,93188,93190,93191,93193,93195,93196,93197,93198,93199,93200,93204,93205,93207,93208,93209,93210,93211,93212,93213,93214,93215,93216,93217,93218,93220,93221,93222,93224,93225,93226,93227,93230,93231,93233,93235,93239,93240,93242,93245,93248,93251);
|
||||
|
||||
COMMIT;
|
||||
52
docs/sql/com-czcf82-data-delete-0601.sql
Normal file
52
docs/sql/com-czcf82-data-delete-0601.sql
Normal file
@@ -0,0 +1,52 @@
|
||||
-- 池州春芳商贸数据清理
|
||||
-- 执行时间:2026-06-01 17:06:41
|
||||
-- 数据范围:93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245
|
||||
|
||||
SET @scope_sql := '93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245';
|
||||
|
||||
START TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_users_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_users WHERE id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eb_user_bak_20260601_170641 AS
|
||||
SELECT * FROM eb_user WHERE uid IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_order_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_order
|
||||
WHERE seller_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245)
|
||||
OR buyer_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_merchandise_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_merchandise WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_selfbonus_log_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_selfbonus_log WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_sharebonus_log_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_sharebonus_log WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_coupon_log_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_coupon_log WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wa_withdraw_bak_20260601_170641 AS
|
||||
SELECT * FROM wa_withdraw WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eb_store_order_bak_20260601_170641 AS
|
||||
SELECT * FROM eb_store_order WHERE uid IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eb_user_integral_record_bak_20260601_170641 AS
|
||||
SELECT * FROM eb_user_integral_record WHERE uid IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
DELETE FROM wa_users WHERE id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM eb_user WHERE uid IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM wa_order WHERE seller_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245) OR buyer_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM wa_merchandise WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM wa_selfbonus_log WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM wa_sharebonus_log WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM wa_coupon_log WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM wa_withdraw WHERE user_id IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM eb_store_order WHERE uid IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
DELETE FROM eb_user_integral_record WHERE uid IN (93119,93132,93133,93134,93156,93163,93172,93173,93183,93187,93188,93190,93191,93193,93195,93197,93198,93199,93209,93212,93214,93215,93220,93221,93224,93226,93230,93235,93239,93240,93245);
|
||||
|
||||
COMMIT;
|
||||
641
docs/sql/run_com_bygsf212_bsy_supplement.py
Normal file
641
docs/sql/run_com_bygsf212_bsy_supplement.py
Normal file
@@ -0,0 +1,641 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Supplement-migrate Bosengyuan team data into bygsf212.
|
||||
|
||||
Default mode is a read-only dry run. Use --execute to back up target tables,
|
||||
insert transformed source rows, and commit the transaction.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pymysql
|
||||
from openpyxl import load_workbook
|
||||
from pymysql.cursors import SSCursor
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DOC = ROOT / "docs" / "com-bygsf212-data-imgration.md"
|
||||
DEFAULT_EXCEL = Path("/Users/mac/Works26/miao-july/宝应鼎信汇/博森元团队成员信息表.xlsx")
|
||||
DEFAULT_DUMP = Path("/Users/mac/Works26/miao-july/宝应鼎信汇/bsy-yangtangyoupin_2026-06-14_14-25-01_mysql_data.sql")
|
||||
EXPECTED_DATABASE = "bygsf212"
|
||||
MERCHANDISE_CUTOFF = "2026-06-12 00:00:00"
|
||||
|
||||
TABLES_ORDER = [
|
||||
"wa_users",
|
||||
"eb_user",
|
||||
"wa_merchandise",
|
||||
"wa_selfbonus_log",
|
||||
"wa_sharebonus_log",
|
||||
"wa_coupon_log",
|
||||
"eb_user_integral_record",
|
||||
]
|
||||
BACKUP_TABLES = [
|
||||
"wa_users",
|
||||
"eb_user",
|
||||
"wa_merchandise",
|
||||
"wa_selfbonus_log",
|
||||
"wa_sharebonus_log",
|
||||
"wa_coupon_log",
|
||||
"eb_user_integral_record",
|
||||
]
|
||||
PK_COLUMNS = {
|
||||
"wa_users": "id",
|
||||
"eb_user": "uid",
|
||||
"wa_merchandise": "id",
|
||||
"wa_selfbonus_log": "id",
|
||||
"wa_sharebonus_log": "id",
|
||||
"wa_coupon_log": "id",
|
||||
"eb_user_integral_record": "id",
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExcelUser:
|
||||
old_id: int
|
||||
nickname: str
|
||||
phone: str
|
||||
parent_old_id: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserDecision:
|
||||
old_id: int
|
||||
target_id: int
|
||||
nickname: str
|
||||
phone: str
|
||||
action: str
|
||||
reason: str
|
||||
|
||||
|
||||
def parse_doc_config() -> dict[str, str]:
|
||||
text = DOC.read_text(encoding="utf-8")
|
||||
|
||||
def grab(name: str) -> str:
|
||||
m = re.search(rf"^\s*{name}:\s*(.+?)\s*$", text, flags=re.M)
|
||||
if not m:
|
||||
raise ValueError(f"missing datasource {name} in {DOC}")
|
||||
return m.group(1).strip()
|
||||
|
||||
return {
|
||||
"host": grab("rds"),
|
||||
"database": grab("name"),
|
||||
"user": grab("username"),
|
||||
"password": grab("password"),
|
||||
}
|
||||
|
||||
|
||||
def load_excel_users(path: Path) -> list[ExcelUser]:
|
||||
wb = load_workbook(path, data_only=True, read_only=True)
|
||||
ws = wb.active
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
headers = [str(x).strip() for x in rows[0]]
|
||||
users: list[ExcelUser] = []
|
||||
for row in rows[1:]:
|
||||
if not any(row):
|
||||
continue
|
||||
data = dict(zip(headers, row))
|
||||
users.append(
|
||||
ExcelUser(
|
||||
old_id=int(data["用户ID"]),
|
||||
nickname=str(data["昵称"]).strip(),
|
||||
phone=str(data["联系方式"]).strip(),
|
||||
parent_old_id=int(data["上级ID"]),
|
||||
)
|
||||
)
|
||||
if len({u.old_id for u in users}) != len(users):
|
||||
raise ValueError("duplicate user ids in Excel")
|
||||
if len({u.phone for u in users}) != len(users):
|
||||
raise ValueError("duplicate phones in Excel")
|
||||
return users
|
||||
|
||||
|
||||
def split_top_level_tuples(values_blob: str) -> list[str]:
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
n = len(values_blob)
|
||||
while i < n:
|
||||
if values_blob[i] != "(":
|
||||
i += 1
|
||||
continue
|
||||
depth = 0
|
||||
in_quote = False
|
||||
start = i
|
||||
j = i
|
||||
while j < n:
|
||||
c = values_blob[j]
|
||||
if in_quote:
|
||||
if c == "\\":
|
||||
j += 2
|
||||
continue
|
||||
if c == "'":
|
||||
if j + 1 < n and values_blob[j + 1] == "'":
|
||||
j += 2
|
||||
continue
|
||||
in_quote = False
|
||||
j += 1
|
||||
continue
|
||||
if c == "'":
|
||||
in_quote = True
|
||||
elif c == "(":
|
||||
depth += 1
|
||||
elif c == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
out.append(values_blob[start : j + 1])
|
||||
j += 1
|
||||
break
|
||||
j += 1
|
||||
i = j
|
||||
return out
|
||||
|
||||
|
||||
def split_mysql_fields(inner: str) -> list[str]:
|
||||
out: list[str] = []
|
||||
cur: list[str] = []
|
||||
i = 0
|
||||
n = len(inner)
|
||||
while i < n:
|
||||
c = inner[i]
|
||||
if c == "'":
|
||||
cur.append(c)
|
||||
i += 1
|
||||
while i < n:
|
||||
c = inner[i]
|
||||
cur.append(c)
|
||||
if c == "\\":
|
||||
if i + 1 < n:
|
||||
cur.append(inner[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
if c == "'":
|
||||
if i + 1 < n and inner[i + 1] == "'":
|
||||
cur.append(inner[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if c == ",":
|
||||
out.append("".join(cur).strip())
|
||||
cur = []
|
||||
i += 1
|
||||
continue
|
||||
cur.append(c)
|
||||
i += 1
|
||||
out.append("".join(cur).strip())
|
||||
return out
|
||||
|
||||
|
||||
def unescape_mysql_string(body: str) -> str:
|
||||
body = body.replace("''", "'")
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
escapes = {
|
||||
"0": "\0",
|
||||
"b": "\b",
|
||||
"n": "\n",
|
||||
"r": "\r",
|
||||
"t": "\t",
|
||||
"Z": "\x1a",
|
||||
"\\": "\\",
|
||||
"'": "'",
|
||||
'"': '"',
|
||||
}
|
||||
while i < len(body):
|
||||
ch = body[i]
|
||||
if ch == "\\" and i + 1 < len(body):
|
||||
nxt = body[i + 1]
|
||||
out.append(escapes.get(nxt, nxt))
|
||||
i += 2
|
||||
continue
|
||||
out.append(ch)
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def parse_sql_value(raw: str) -> Any:
|
||||
raw = raw.strip()
|
||||
if raw.upper() == "NULL":
|
||||
return None
|
||||
if raw.startswith("'") and raw.endswith("'"):
|
||||
return unescape_mysql_string(raw[1:-1])
|
||||
if re.fullmatch(r"-?\d+", raw):
|
||||
return int(raw)
|
||||
if re.fullmatch(r"-?\d+\.\d+", raw):
|
||||
return Decimal(raw)
|
||||
return raw
|
||||
|
||||
|
||||
def parse_dump(dump: Path, source_ids: set[int]) -> tuple[dict[str, list[str]], dict[str, list[list[Any]]]]:
|
||||
if not dump.is_file():
|
||||
raise FileNotFoundError(dump)
|
||||
schemas: dict[str, list[str]] = {}
|
||||
rows: dict[str, list[list[Any]]] = {table: [] for table in TABLES_ORDER}
|
||||
current: str | None = None
|
||||
columns: list[str] = []
|
||||
|
||||
with dump.open("r", encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
if line.startswith("CREATE TABLE `"):
|
||||
name = line.split("`", 2)[1]
|
||||
current = name if name in TABLES_ORDER else None
|
||||
columns = []
|
||||
continue
|
||||
if current:
|
||||
m = re.match(r"\s*`([^`]+)`", line)
|
||||
if m:
|
||||
columns.append(m.group(1))
|
||||
if line.startswith(")") or line.startswith("ENGINE="):
|
||||
schemas[current] = columns
|
||||
current = None
|
||||
continue
|
||||
if not line.startswith("INSERT INTO `"):
|
||||
continue
|
||||
table = line.split("`", 2)[1]
|
||||
if table not in TABLES_ORDER:
|
||||
continue
|
||||
if table not in schemas:
|
||||
raise ValueError(f"INSERT before schema for {table}")
|
||||
blob = line[line.index("VALUES") + len("VALUES") :].strip()
|
||||
if blob.endswith(";"):
|
||||
blob = blob[:-1].strip()
|
||||
for tup in split_top_level_tuples(blob):
|
||||
values = [parse_sql_value(x) for x in split_mysql_fields(tup.strip()[1:-1])]
|
||||
if keep_source_row(table, schemas[table], values, source_ids):
|
||||
rows[table].append(values)
|
||||
missing = [table for table in TABLES_ORDER if table not in schemas]
|
||||
if missing:
|
||||
raise ValueError(f"missing schemas in dump: {missing}")
|
||||
return schemas, rows
|
||||
|
||||
|
||||
def keep_source_row(table: str, columns: list[str], row: list[Any], source_ids: set[int]) -> bool:
|
||||
idx = {name: i for i, name in enumerate(columns)}
|
||||
if table == "wa_users":
|
||||
return int(row[idx["id"]]) in source_ids
|
||||
if table == "eb_user":
|
||||
return int(row[idx["uid"]]) in source_ids
|
||||
if table == "wa_merchandise":
|
||||
return int(row[idx["user_id"]]) in source_ids and str(row[idx["created_at"]]) >= MERCHANDISE_CUTOFF
|
||||
if table in {"wa_selfbonus_log", "wa_sharebonus_log", "wa_coupon_log"}:
|
||||
return int(row[idx["user_id"]]) in source_ids
|
||||
if table == "eb_user_integral_record":
|
||||
return int(row[idx["uid"]]) in source_ids
|
||||
return False
|
||||
|
||||
|
||||
def connect(config: dict[str, str], cursorclass=None):
|
||||
kwargs = {
|
||||
"host": config["host"],
|
||||
"user": config["user"],
|
||||
"password": config["password"],
|
||||
"database": config["database"],
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"connect_timeout": 10,
|
||||
"read_timeout": 120,
|
||||
"write_timeout": 120,
|
||||
}
|
||||
if cursorclass is not None:
|
||||
kwargs["cursorclass"] = cursorclass
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def get_target_schemas(cur) -> dict[str, list[str]]:
|
||||
cur.execute("SELECT DATABASE()")
|
||||
database = cur.fetchone()[0]
|
||||
if database != EXPECTED_DATABASE:
|
||||
raise RuntimeError(f"refusing to run against database {database!r}")
|
||||
schemas: dict[str, list[str]] = {}
|
||||
for table in BACKUP_TABLES:
|
||||
cur.execute(f"SHOW COLUMNS FROM `{table}`")
|
||||
schemas[table] = [row[0] for row in cur.fetchall()]
|
||||
return schemas
|
||||
|
||||
|
||||
def table_auto_increment(cur, table: str) -> int:
|
||||
cur.execute("SHOW TABLE STATUS LIKE %s", (table,))
|
||||
row = cur.fetchone()
|
||||
return int(row[10] or 1)
|
||||
|
||||
|
||||
def existing_pk_set(cur, table: str, pk_col: str) -> set[int]:
|
||||
cur.execute(f"SELECT `{pk_col}` FROM `{table}`")
|
||||
return {int(row[0]) for row in cur.fetchall()}
|
||||
|
||||
|
||||
def determine_user_mapping(cur, users: list[ExcelUser]) -> list[UserDecision]:
|
||||
old_ids = [u.old_id for u in users]
|
||||
phones = [u.phone for u in users]
|
||||
old_clause = ",".join(["%s"] * len(old_ids))
|
||||
phone_clause = ",".join(["%s"] * len(phones))
|
||||
|
||||
cur.execute(f"SELECT id,nickname,mobile FROM `wa_users` WHERE id IN ({old_clause})", old_ids)
|
||||
by_id = {int(row[0]): {"nickname": row[1], "phone": str(row[2])} for row in cur.fetchall()}
|
||||
cur.execute(f"SELECT id,nickname,mobile FROM `wa_users` WHERE mobile IN ({phone_clause})", phones)
|
||||
by_phone = {str(row[2]): {"id": int(row[0]), "nickname": row[1]} for row in cur.fetchall()}
|
||||
|
||||
all_user_ids = existing_pk_set(cur, "wa_users", "id") | existing_pk_set(cur, "eb_user", "uid")
|
||||
next_id = max(table_auto_increment(cur, "wa_users"), max(all_user_ids or {0}) + 1)
|
||||
decisions: list[UserDecision] = []
|
||||
|
||||
def next_free_id() -> int:
|
||||
nonlocal next_id
|
||||
while next_id in all_user_ids:
|
||||
next_id += 1
|
||||
value = next_id
|
||||
all_user_ids.add(value)
|
||||
next_id += 1
|
||||
return value
|
||||
|
||||
for user in users:
|
||||
existing_same_phone = by_phone.get(user.phone)
|
||||
existing_same_id = by_id.get(user.old_id)
|
||||
if existing_same_phone:
|
||||
decisions.append(
|
||||
UserDecision(
|
||||
old_id=user.old_id,
|
||||
target_id=existing_same_phone["id"],
|
||||
nickname=user.nickname,
|
||||
phone=user.phone,
|
||||
action="skip_existing_phone",
|
||||
reason=f"phone already exists as uid={existing_same_phone['id']}",
|
||||
)
|
||||
)
|
||||
continue
|
||||
if not existing_same_id:
|
||||
all_user_ids.add(user.old_id)
|
||||
decisions.append(
|
||||
UserDecision(
|
||||
old_id=user.old_id,
|
||||
target_id=user.old_id,
|
||||
nickname=user.nickname,
|
||||
phone=user.phone,
|
||||
action="insert_original_id",
|
||||
reason="old id not present in target",
|
||||
)
|
||||
)
|
||||
continue
|
||||
target_id = next_free_id()
|
||||
decisions.append(
|
||||
UserDecision(
|
||||
old_id=user.old_id,
|
||||
target_id=target_id,
|
||||
nickname=user.nickname,
|
||||
phone=user.phone,
|
||||
action="insert_reassigned_id",
|
||||
reason=(
|
||||
f"old id occupied by {existing_same_id['nickname']}/"
|
||||
f"{existing_same_id['phone']}"
|
||||
),
|
||||
)
|
||||
)
|
||||
if len({d.target_id for d in decisions}) != len(decisions):
|
||||
raise ValueError("target user id collision in decisions")
|
||||
return decisions
|
||||
|
||||
|
||||
def allocate_pk_maps(
|
||||
cur, source_rows: dict[str, list[list[Any]]], schemas: dict[str, list[str]]
|
||||
) -> dict[str, dict[int, int]]:
|
||||
maps: dict[str, dict[int, int]] = {}
|
||||
for table, rows in source_rows.items():
|
||||
if table in {"wa_users", "eb_user"}:
|
||||
continue
|
||||
pk_col = PK_COLUMNS[table]
|
||||
pk_idx = schemas[table].index(pk_col)
|
||||
source_pks = [int(row[pk_idx]) for row in rows]
|
||||
existing = existing_pk_set(cur, table, pk_col)
|
||||
used = set(existing) | set(source_pks)
|
||||
auto = table_auto_increment(cur, table)
|
||||
next_id = max([auto, *(used or {0})]) + 1
|
||||
table_map: dict[int, int] = {}
|
||||
for old_pk in source_pks:
|
||||
if old_pk in table_map:
|
||||
continue
|
||||
if old_pk not in existing:
|
||||
table_map[old_pk] = old_pk
|
||||
continue
|
||||
while next_id in used:
|
||||
next_id += 1
|
||||
table_map[old_pk] = next_id
|
||||
used.add(next_id)
|
||||
next_id += 1
|
||||
maps[table] = table_map
|
||||
return maps
|
||||
|
||||
|
||||
def transform_rows(
|
||||
source_rows: dict[str, list[list[Any]]],
|
||||
schemas: dict[str, list[str]],
|
||||
decisions: list[UserDecision],
|
||||
pk_maps: dict[str, dict[int, int]],
|
||||
) -> dict[str, list[list[Any]]]:
|
||||
user_map = {d.old_id: d.target_id for d in decisions}
|
||||
insert_user_ids = {d.old_id for d in decisions if d.action.startswith("insert_")}
|
||||
transformed: dict[str, list[list[Any]]] = {table: [] for table in TABLES_ORDER}
|
||||
|
||||
for table in TABLES_ORDER:
|
||||
cols = schemas[table]
|
||||
idx = {name: i for i, name in enumerate(cols)}
|
||||
for source in source_rows[table]:
|
||||
row = list(source)
|
||||
if table == "wa_users":
|
||||
old_id = int(row[idx["id"]])
|
||||
if old_id not in insert_user_ids:
|
||||
continue
|
||||
row[idx["id"]] = user_map[old_id]
|
||||
if int(row[idx["pid"]] or 0) in user_map:
|
||||
row[idx["pid"]] = user_map[int(row[idx["pid"]])]
|
||||
elif table == "eb_user":
|
||||
old_id = int(row[idx["uid"]])
|
||||
if old_id not in insert_user_ids:
|
||||
continue
|
||||
row[idx["uid"]] = user_map[old_id]
|
||||
if int(row[idx["spread_uid"]] or 0) in user_map:
|
||||
row[idx["spread_uid"]] = user_map[int(row[idx["spread_uid"]])]
|
||||
elif table == "wa_merchandise":
|
||||
old_pk = int(row[idx["id"]])
|
||||
old_user_id = int(row[idx["user_id"]])
|
||||
if old_user_id not in insert_user_ids:
|
||||
continue
|
||||
row[idx["id"]] = pk_maps[table][old_pk]
|
||||
row[idx["user_id"]] = user_map[old_user_id]
|
||||
elif table in {"wa_selfbonus_log", "wa_sharebonus_log", "wa_coupon_log"}:
|
||||
old_pk = int(row[idx["id"]])
|
||||
old_user_id = int(row[idx["user_id"]])
|
||||
if old_user_id not in insert_user_ids:
|
||||
continue
|
||||
row[idx["id"]] = pk_maps[table][old_pk]
|
||||
row[idx["user_id"]] = user_map[old_user_id]
|
||||
elif table == "eb_user_integral_record":
|
||||
old_pk = int(row[idx["id"]])
|
||||
old_user_id = int(row[idx["uid"]])
|
||||
if old_user_id not in insert_user_ids:
|
||||
continue
|
||||
row[idx["id"]] = pk_maps[table][old_pk]
|
||||
row[idx["uid"]] = user_map[old_user_id]
|
||||
update_integral_links(row, idx, pk_maps.get("wa_selfbonus_log", {}))
|
||||
transformed[table].append(row)
|
||||
return transformed
|
||||
|
||||
|
||||
def update_integral_links(row: list[Any], idx: dict[str, int], selfbonus_map: dict[int, int]) -> None:
|
||||
log_id = row[idx["wa_selfbonus_logid"]]
|
||||
if log_id is not None and int(log_id) in selfbonus_map:
|
||||
row[idx["wa_selfbonus_logid"]] = selfbonus_map[int(log_id)]
|
||||
if str(row[idx["link_type"]]) == "selfbonus":
|
||||
try:
|
||||
link_id = int(str(row[idx["link_id"]]))
|
||||
except ValueError:
|
||||
return
|
||||
if link_id in selfbonus_map:
|
||||
row[idx["link_id"]] = str(selfbonus_map[link_id])
|
||||
|
||||
|
||||
def backup_tables(config: dict[str, str], backup_path: Path) -> None:
|
||||
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
backup_conn = connect(config, cursorclass=SSCursor)
|
||||
try:
|
||||
with gzip.open(backup_path, "wt", encoding="utf-8") as out:
|
||||
out.write("-- bygsf212 bsy supplement backup\n")
|
||||
out.write(f"-- created_at: {datetime.now().isoformat(timespec='seconds')}\n")
|
||||
out.write("SET NAMES utf8mb4;\n")
|
||||
for table in BACKUP_TABLES:
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SHOW CREATE TABLE `{table}`")
|
||||
create_sql = cur.fetchone()[1]
|
||||
out.write(f"\n-- Table `{table}`\n")
|
||||
out.write(f"DROP TABLE IF EXISTS `{table}`;\n")
|
||||
out.write(create_sql + ";\n")
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SELECT * FROM `{table}`")
|
||||
batch: list[str] = []
|
||||
row_count = 0
|
||||
for row in cur:
|
||||
batch.append("(" + ",".join(backup_conn.literal(v) for v in row) + ")")
|
||||
row_count += 1
|
||||
if len(batch) >= 200:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
batch = []
|
||||
if batch:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
print(f"backup {table}: rows={row_count}")
|
||||
finally:
|
||||
backup_conn.close()
|
||||
|
||||
|
||||
def insert_rows(cur, target_schemas: dict[str, list[str]], rows_by_table: dict[str, list[list[Any]]]) -> dict[str, int]:
|
||||
inserted: dict[str, int] = {}
|
||||
for table in TABLES_ORDER:
|
||||
rows = rows_by_table[table]
|
||||
inserted[table] = 0
|
||||
if not rows:
|
||||
continue
|
||||
cols = target_schemas[table]
|
||||
col_sql = ",".join(f"`{col}`" for col in cols)
|
||||
ph = ",".join(["%s"] * len(cols))
|
||||
sql = f"INSERT INTO `{table}` ({col_sql}) VALUES ({ph})"
|
||||
for row in rows:
|
||||
cur.execute(sql, tuple(row))
|
||||
inserted[table] += cur.rowcount
|
||||
return inserted
|
||||
|
||||
|
||||
def summarize_pk_remaps(pk_maps: dict[str, dict[int, int]]) -> dict[str, int]:
|
||||
return {
|
||||
table: sum(1 for old, new in mapping.items() if old != new)
|
||||
for table, mapping in pk_maps.items()
|
||||
}
|
||||
|
||||
|
||||
def print_summary(
|
||||
users: list[ExcelUser],
|
||||
source_rows: dict[str, list[list[Any]]],
|
||||
transformed_rows: dict[str, list[list[Any]]],
|
||||
decisions: list[UserDecision],
|
||||
pk_maps: dict[str, dict[int, int]],
|
||||
) -> None:
|
||||
print(f"excel_users={len(users)}")
|
||||
print("user_mapping")
|
||||
for d in decisions:
|
||||
suffix = "" if d.old_id == d.target_id else f" -> {d.target_id}"
|
||||
print(f" {d.old_id}{suffix}: {d.nickname}/{d.phone} [{d.action}] {d.reason}")
|
||||
print("source_rows")
|
||||
for table in TABLES_ORDER:
|
||||
print(f" {table}: {len(source_rows[table])}")
|
||||
print("insert_rows")
|
||||
for table in TABLES_ORDER:
|
||||
print(f" {table}: {len(transformed_rows[table])}")
|
||||
print("pk_remaps")
|
||||
print(json.dumps(summarize_pk_remaps(pk_maps), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--execute", action="store_true", help="insert rows and commit")
|
||||
parser.add_argument("--excel", type=Path, default=DEFAULT_EXCEL)
|
||||
parser.add_argument("--dump", type=Path, default=DEFAULT_DUMP)
|
||||
parser.add_argument("--backup-dir", type=Path, default=ROOT / "docs" / "sql" / "backups")
|
||||
args = parser.parse_args()
|
||||
|
||||
config = parse_doc_config()
|
||||
users = load_excel_users(args.excel)
|
||||
source_ids = {u.old_id for u in users}
|
||||
dump_schemas, source_rows = parse_dump(args.dump, source_ids)
|
||||
|
||||
conn = connect(config)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
target_schemas = get_target_schemas(cur)
|
||||
for table in TABLES_ORDER:
|
||||
if dump_schemas[table] != target_schemas[table]:
|
||||
raise RuntimeError(f"schema mismatch for {table}")
|
||||
decisions = determine_user_mapping(cur, users)
|
||||
pk_maps = allocate_pk_maps(cur, source_rows, dump_schemas)
|
||||
transformed_rows = transform_rows(source_rows, dump_schemas, decisions, pk_maps)
|
||||
print_summary(users, source_rows, transformed_rows, decisions, pk_maps)
|
||||
if not args.execute:
|
||||
print("dry_run_only=true")
|
||||
conn.rollback()
|
||||
return 0
|
||||
|
||||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = args.backup_dir / f"bygsf212_bsy_supplement_before_{stamp}.sql.gz"
|
||||
print(f"backup_path={backup_path}")
|
||||
backup_tables(config, backup_path)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 0")
|
||||
inserted = insert_rows(cur, target_schemas, transformed_rows)
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
print("inserted_rows")
|
||||
print(json.dumps(inserted, ensure_ascii=False, indent=2))
|
||||
conn.commit()
|
||||
print("COMMIT ok")
|
||||
return 0
|
||||
except Exception as exc:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
except Exception:
|
||||
pass
|
||||
conn.rollback()
|
||||
print(f"ROLLBACK: {exc}")
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
401
docs/sql/run_com_bygsf212_cleanup.py
Normal file
401
docs/sql/run_com_bygsf212_cleanup.py
Normal file
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run data cleanup for docs/com-bygsf212-data-imgration.md.
|
||||
|
||||
Default mode is a read-only dry run. Use --execute to create a local SQL backup,
|
||||
delete rows according to the migration document, and commit the transaction.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pymysql
|
||||
from pymysql.cursors import SSCursor
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DOC = ROOT / "docs" / "com-bygsf212-data-imgration.md"
|
||||
DEFAULT_DUMPS = [
|
||||
Path("/Users/mac/Works26/miao-july/宝应鼎信汇/bsy-yangtangyoupin_2026-06-14_14-25-01_mysql_data.sql"),
|
||||
Path("/Users/mac/Works26/miao-july/宝应鼎信汇/jyw-yangtangyoupin_2026-06-14_14-55-01_mysql_data.sql"),
|
||||
]
|
||||
CUTOFF = "2026-06-12 00:00:00"
|
||||
EXPECTED_DATABASE = "bygsf212"
|
||||
TABLES = [
|
||||
"wa_order",
|
||||
"wa_withdraw",
|
||||
"eb_store_order",
|
||||
"wa_merchandise",
|
||||
"wa_selfbonus_log",
|
||||
"wa_sharebonus_log",
|
||||
"wa_coupon_log",
|
||||
"eb_user_integral_record",
|
||||
"eb_user",
|
||||
"wa_users",
|
||||
]
|
||||
FILTERS = {
|
||||
"wa_users": "id",
|
||||
"eb_user": "uid",
|
||||
"wa_selfbonus_log": "user_id",
|
||||
"wa_sharebonus_log": "user_id",
|
||||
"wa_coupon_log": "user_id",
|
||||
"eb_user_integral_record": "uid",
|
||||
}
|
||||
CLEAR_TABLES = ["wa_order", "wa_withdraw", "eb_store_order"]
|
||||
|
||||
|
||||
def parse_doc() -> tuple[dict[str, str], list[int]]:
|
||||
text = DOC.read_text(encoding="utf-8")
|
||||
|
||||
def grab(name: str) -> str:
|
||||
m = re.search(rf"^\s*{name}:\s*(.+?)\s*$", text, flags=re.M)
|
||||
if not m:
|
||||
raise ValueError(f"missing datasource {name} in {DOC}")
|
||||
return m.group(1).strip()
|
||||
|
||||
ids_match = re.search(r"保留名单:\s*\n\s*`([^`]+)`", text)
|
||||
if not ids_match:
|
||||
raise ValueError(f"missing user id keep list in {DOC}")
|
||||
ids = [int(x.strip()) for x in ids_match.group(1).split(",") if x.strip()]
|
||||
if len(ids) != len(set(ids)):
|
||||
raise ValueError("duplicate ids in keep list")
|
||||
config = {
|
||||
"host": grab("rds"),
|
||||
"database": grab("name"),
|
||||
"user": grab("username"),
|
||||
"password": grab("password"),
|
||||
}
|
||||
return config, ids
|
||||
|
||||
|
||||
def split_top_level_tuples(values_blob: str) -> list[str]:
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
n = len(values_blob)
|
||||
while i < n:
|
||||
if values_blob[i] != "(":
|
||||
i += 1
|
||||
continue
|
||||
depth = 0
|
||||
in_quote = False
|
||||
start = i
|
||||
j = i
|
||||
while j < n:
|
||||
c = values_blob[j]
|
||||
if in_quote:
|
||||
if c == "\\":
|
||||
j += 2
|
||||
continue
|
||||
if c == "'":
|
||||
if j + 1 < n and values_blob[j + 1] == "'":
|
||||
j += 2
|
||||
continue
|
||||
in_quote = False
|
||||
j += 1
|
||||
continue
|
||||
if c == "'":
|
||||
in_quote = True
|
||||
elif c == "(":
|
||||
depth += 1
|
||||
elif c == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
out.append(values_blob[start : j + 1])
|
||||
j += 1
|
||||
break
|
||||
j += 1
|
||||
i = j
|
||||
return out
|
||||
|
||||
|
||||
def split_mysql_fields(inner: str) -> list[str]:
|
||||
out: list[str] = []
|
||||
cur: list[str] = []
|
||||
i = 0
|
||||
n = len(inner)
|
||||
while i < n:
|
||||
c = inner[i]
|
||||
if c == "'":
|
||||
cur.append(c)
|
||||
i += 1
|
||||
while i < n:
|
||||
c = inner[i]
|
||||
cur.append(c)
|
||||
if c == "\\":
|
||||
if i + 1 < n:
|
||||
cur.append(inner[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
if c == "'":
|
||||
if i + 1 < n and inner[i + 1] == "'":
|
||||
cur.append(inner[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if c == ",":
|
||||
out.append("".join(cur).strip())
|
||||
cur = []
|
||||
i += 1
|
||||
continue
|
||||
cur.append(c)
|
||||
i += 1
|
||||
out.append("".join(cur).strip())
|
||||
return out
|
||||
|
||||
|
||||
def unquote_sql_string(raw: str) -> str:
|
||||
raw = raw.strip()
|
||||
if raw.startswith("'") and raw.endswith("'"):
|
||||
body = raw[1:-1]
|
||||
body = body.replace("''", "'")
|
||||
body = body.replace("\\'", "'").replace("\\\\", "\\")
|
||||
return body
|
||||
return raw
|
||||
|
||||
|
||||
def extract_wa_merchandise_keep_ids(dumps: list[Path], keep_users: set[int]) -> list[int]:
|
||||
keep_ids: set[int] = set()
|
||||
for dump in dumps:
|
||||
if not dump.is_file():
|
||||
raise FileNotFoundError(f"dump not found: {dump}")
|
||||
total_rows = 0
|
||||
insert_lines = 0
|
||||
file_keep_ids: set[int] = set()
|
||||
with dump.open("r", encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
if "INSERT INTO `wa_merchandise`" not in line or "VALUES" not in line:
|
||||
continue
|
||||
insert_lines += 1
|
||||
blob = line[line.index("VALUES") + len("VALUES") :].strip()
|
||||
if blob.endswith(";"):
|
||||
blob = blob[:-1].strip()
|
||||
for tup in split_top_level_tuples(blob):
|
||||
total_rows += 1
|
||||
fields = split_mysql_fields(tup.strip()[1:-1])
|
||||
if len(fields) < 9:
|
||||
raise ValueError(f"malformed wa_merchandise tuple: {tup[:120]}")
|
||||
row_id = int(fields[0])
|
||||
user_id = int(fields[2])
|
||||
created_at = unquote_sql_string(fields[8])
|
||||
if created_at >= CUTOFF and user_id in keep_users:
|
||||
file_keep_ids.add(row_id)
|
||||
if insert_lines == 0:
|
||||
raise ValueError(f"no INSERT INTO `wa_merchandise` found in dump: {dump}")
|
||||
keep_ids.update(file_keep_ids)
|
||||
print(f"dump={dump.name} wa_merchandise_rows={total_rows} keep_by_rule={len(file_keep_ids)}")
|
||||
return sorted(keep_ids)
|
||||
|
||||
|
||||
def connect(config: dict[str, str], cursorclass=None):
|
||||
kwargs = {
|
||||
"host": config["host"],
|
||||
"user": config["user"],
|
||||
"password": config["password"],
|
||||
"database": config["database"],
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"connect_timeout": 10,
|
||||
"read_timeout": 120,
|
||||
"write_timeout": 120,
|
||||
}
|
||||
if cursorclass is not None:
|
||||
kwargs["cursorclass"] = cursorclass
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def placeholders(items: list[int] | set[int]) -> str:
|
||||
if not items:
|
||||
return "NULL"
|
||||
return ",".join(["%s"] * len(items))
|
||||
|
||||
|
||||
def count_where(cur, table: str, where: str = "1=1", params: tuple[Any, ...] = ()) -> int:
|
||||
cur.execute(f"SELECT COUNT(*) FROM `{table}` WHERE {where}", params)
|
||||
return int(cur.fetchone()[0])
|
||||
|
||||
|
||||
def inspect_schema(cur) -> None:
|
||||
cur.execute("SELECT DATABASE()")
|
||||
database = cur.fetchone()[0]
|
||||
if database != EXPECTED_DATABASE:
|
||||
raise RuntimeError(f"refusing to run against database {database!r}")
|
||||
for table in TABLES:
|
||||
cur.execute("SHOW TABLES LIKE %s", (table,))
|
||||
if not cur.fetchone():
|
||||
raise RuntimeError(f"missing table `{table}`")
|
||||
required = {
|
||||
"wa_users": {"id"},
|
||||
"eb_user": {"uid"},
|
||||
"wa_order": set(),
|
||||
"wa_withdraw": set(),
|
||||
"eb_store_order": set(),
|
||||
"wa_merchandise": {"id", "user_id", "created_at"},
|
||||
"wa_selfbonus_log": {"user_id"},
|
||||
"wa_sharebonus_log": {"user_id"},
|
||||
"wa_coupon_log": {"user_id"},
|
||||
"eb_user_integral_record": {"uid"},
|
||||
}
|
||||
for table, cols in required.items():
|
||||
if not cols:
|
||||
continue
|
||||
cur.execute(f"SHOW COLUMNS FROM `{table}`")
|
||||
actual = {row[0] for row in cur.fetchall()}
|
||||
missing = cols - actual
|
||||
if missing:
|
||||
raise RuntimeError(f"table `{table}` missing columns: {sorted(missing)}")
|
||||
|
||||
|
||||
def collect_counts(cur, keep_users: list[int], keep_merchandise_ids: list[int]) -> dict[str, dict[str, int]]:
|
||||
counts: dict[str, dict[str, int]] = {}
|
||||
user_clause = placeholders(keep_users)
|
||||
merch_clause = placeholders(keep_merchandise_ids)
|
||||
for table in CLEAR_TABLES:
|
||||
total = count_where(cur, table)
|
||||
counts[table] = {"before": total, "keep": 0, "delete": total}
|
||||
for table, col in FILTERS.items():
|
||||
total = count_where(cur, table)
|
||||
keep = count_where(cur, table, f"`{col}` IN ({user_clause})", tuple(keep_users))
|
||||
counts[table] = {"before": total, "keep": keep, "delete": total - keep}
|
||||
total = count_where(cur, "wa_merchandise")
|
||||
keep = (
|
||||
count_where(cur, "wa_merchandise", f"`id` IN ({merch_clause})", tuple(keep_merchandise_ids))
|
||||
if keep_merchandise_ids
|
||||
else 0
|
||||
)
|
||||
counts["wa_merchandise"] = {"before": total, "keep": keep, "delete": total - keep}
|
||||
return counts
|
||||
|
||||
|
||||
def print_counts(title: str, counts: dict[str, dict[str, int]]) -> None:
|
||||
print(title)
|
||||
for table in TABLES:
|
||||
c = counts[table]
|
||||
print(f" {table}: before={c['before']} keep={c['keep']} delete={c['delete']}")
|
||||
|
||||
|
||||
def backup_tables(config: dict[str, str], backup_path: Path) -> None:
|
||||
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
backup_conn = connect(config, cursorclass=SSCursor)
|
||||
try:
|
||||
with gzip.open(backup_path, "wt", encoding="utf-8") as out:
|
||||
out.write("-- bygsf212 cleanup backup\n")
|
||||
out.write(f"-- created_at: {datetime.now().isoformat(timespec='seconds')}\n")
|
||||
out.write("SET NAMES utf8mb4;\n")
|
||||
for table in TABLES:
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SHOW CREATE TABLE `{table}`")
|
||||
row = cur.fetchone()
|
||||
create_sql = row[1]
|
||||
out.write(f"\n-- Table `{table}`\n")
|
||||
out.write(f"DROP TABLE IF EXISTS `{table}`;\n")
|
||||
out.write(create_sql + ";\n")
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SELECT * FROM `{table}`")
|
||||
batch: list[str] = []
|
||||
row_count = 0
|
||||
for row in cur:
|
||||
batch.append("(" + ",".join(backup_conn.literal(v) for v in row) + ")")
|
||||
row_count += 1
|
||||
if len(batch) >= 200:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
batch = []
|
||||
if batch:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
print(f"backup {table}: rows={row_count}")
|
||||
finally:
|
||||
backup_conn.close()
|
||||
|
||||
|
||||
def execute_cleanup(cur, keep_users: list[int], keep_merchandise_ids: list[int]) -> dict[str, int]:
|
||||
user_clause = placeholders(keep_users)
|
||||
deleted: dict[str, int] = {}
|
||||
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 0")
|
||||
for table in CLEAR_TABLES:
|
||||
cur.execute(f"DELETE FROM `{table}`")
|
||||
deleted[table] = cur.rowcount
|
||||
if keep_merchandise_ids:
|
||||
merch_clause = placeholders(keep_merchandise_ids)
|
||||
cur.execute(f"DELETE FROM `wa_merchandise` WHERE `id` NOT IN ({merch_clause})", tuple(keep_merchandise_ids))
|
||||
else:
|
||||
cur.execute("DELETE FROM `wa_merchandise`")
|
||||
deleted["wa_merchandise"] = cur.rowcount
|
||||
for table in ["wa_selfbonus_log", "wa_sharebonus_log", "wa_coupon_log"]:
|
||||
cur.execute(f"DELETE FROM `{table}` WHERE `user_id` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted[table] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `eb_user_integral_record` WHERE `uid` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted["eb_user_integral_record"] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `eb_user` WHERE `uid` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted["eb_user"] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `wa_users` WHERE `id` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted["wa_users"] = cur.rowcount
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
return deleted
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--execute", action="store_true", help="perform DELETEs and COMMIT")
|
||||
parser.add_argument("--dump", action="append", type=Path, dest="dumps")
|
||||
parser.add_argument("--backup-dir", type=Path, default=ROOT / "docs" / "sql" / "backups")
|
||||
args = parser.parse_args()
|
||||
|
||||
dumps = args.dumps if args.dumps else DEFAULT_DUMPS
|
||||
config, keep_users = parse_doc()
|
||||
keep_merchandise_ids = extract_wa_merchandise_keep_ids(dumps, set(keep_users))
|
||||
print(f"keep_user_ids={len(keep_users)} keep_wa_merchandise_ids={len(keep_merchandise_ids)}")
|
||||
|
||||
conn = connect(config)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
inspect_schema(cur)
|
||||
before = collect_counts(cur, keep_users, keep_merchandise_ids)
|
||||
print_counts("before_counts", before)
|
||||
if not args.execute:
|
||||
print("dry_run_only=true")
|
||||
conn.rollback()
|
||||
return 0
|
||||
|
||||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = args.backup_dir / f"bygsf212_cleanup_before_{stamp}.sql.gz"
|
||||
print(f"backup_path={backup_path}")
|
||||
backup_tables(config, backup_path)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
deleted = execute_cleanup(cur, keep_users, keep_merchandise_ids)
|
||||
after = collect_counts(cur, keep_users, keep_merchandise_ids)
|
||||
print("deleted_rows")
|
||||
print(json.dumps(deleted, ensure_ascii=False, indent=2))
|
||||
print_counts("after_counts_before_commit", after)
|
||||
conn.commit()
|
||||
print("COMMIT ok")
|
||||
|
||||
with conn.cursor() as cur:
|
||||
final_counts = collect_counts(cur, keep_users, keep_merchandise_ids)
|
||||
print_counts("final_counts", final_counts)
|
||||
return 0
|
||||
except Exception as e:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
except Exception:
|
||||
pass
|
||||
conn.rollback()
|
||||
print(f"ROLLBACK: {e}", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
372
docs/sql/run_com_bygsf212_remove_conflict_users.py
Normal file
372
docs/sql/run_com_bygsf212_remove_conflict_users.py
Normal file
@@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove four conflict users from bygsf212.
|
||||
|
||||
Targets:
|
||||
93251 龚华侨
|
||||
93272 杜紅梅/杜红梅
|
||||
93273 戴庆宏
|
||||
93276 陈晓平
|
||||
|
||||
Default mode is a read-only dry run. Use --execute to back up affected rows,
|
||||
delete related user data, and commit the transaction.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pymysql
|
||||
from pymysql.cursors import SSCursor
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DOC = ROOT / "docs" / "com-bygsf212-data-imgration.md"
|
||||
EXPECTED_DATABASE = "bygsf212"
|
||||
|
||||
TARGETS = {
|
||||
93251: ("龚华侨", "15952530725"),
|
||||
93272: ("杜紅梅", "13952547832"),
|
||||
93273: ("戴庆宏", "15000637090"),
|
||||
93276: ("陈晓平", "15995103126"),
|
||||
}
|
||||
|
||||
# Tables that can contain rows owned by these users. Keep 0-row tables here so
|
||||
# the script remains safe if the cleanup is run after new related data appears.
|
||||
DELETE_CONDITIONS = {
|
||||
"wa_order": ["`seller_id` IN ({ids}) OR `buyer_id` IN ({ids})"],
|
||||
"wa_merchandise": ["`user_id` IN ({ids})"],
|
||||
"wa_selfbonus_log": ["`user_id` IN ({ids})"],
|
||||
"wa_sharebonus_log": ["`user_id` IN ({ids})"],
|
||||
"wa_coupon_log": ["`user_id` IN ({ids})"],
|
||||
"wa_withdraw": ["`user_id` IN ({ids})"],
|
||||
"wa_address": ["`user_id` IN ({ids})"],
|
||||
"wa_alipay": ["`user_id` IN ({ids})"],
|
||||
"wa_bank": ["`user_id` IN ({ids})"],
|
||||
"wa_money_log": ["`user_id` IN ({ids})"],
|
||||
"eb_ali_pay_info": ["`seller_id` IN ({ids})"],
|
||||
"eb_article": ["`uid` IN ({ids})"],
|
||||
"eb_sms_record": ["`uid` IN ({ids})"],
|
||||
"eb_store_bargain_user": ["`uid` IN ({ids})"],
|
||||
"eb_store_bargain_user_help": ["`uid` IN ({ids})"],
|
||||
"eb_store_cart": ["`uid` IN ({ids})"],
|
||||
"eb_store_coupon_user": ["`uid` IN ({ids})"],
|
||||
"eb_store_order": ["`uid` IN ({ids})"],
|
||||
"eb_store_pink": ["`uid` IN ({ids})"],
|
||||
"eb_store_product_log": ["`uid` IN ({ids}) OR `pay_uid` IN ({ids})"],
|
||||
"eb_store_product_relation": ["`uid` IN ({ids})"],
|
||||
"eb_store_product_reply": ["`uid` IN ({ids})"],
|
||||
"eb_system_store_staff": ["`uid` IN ({ids})"],
|
||||
"eb_user_address": ["`uid` IN ({ids})"],
|
||||
"eb_user_bill": ["`uid` IN ({ids})"],
|
||||
"eb_user_brokerage_record": ["`uid` IN ({ids})"],
|
||||
"eb_user_experience_record": ["`uid` IN ({ids})"],
|
||||
"eb_user_extract": ["`uid` IN ({ids})"],
|
||||
"eb_user_integral_record": ["`uid` IN ({ids})"],
|
||||
"eb_user_level": ["`uid` IN ({ids})"],
|
||||
"eb_user_recharge": ["`uid` IN ({ids})"],
|
||||
"eb_user_sign": ["`uid` IN ({ids})"],
|
||||
"eb_user_token": ["`uid` IN ({ids})"],
|
||||
"eb_user_visit_record": ["`uid` IN ({ids})"],
|
||||
"t_platform_account": ["`user_id` IN ({ids})"],
|
||||
"eb_user": ["`uid` IN ({ids})"],
|
||||
"wa_users": ["`id` IN ({ids})"],
|
||||
}
|
||||
|
||||
UPDATE_CONDITIONS = {
|
||||
"wa_users": "`pid` IN ({ids}) AND `id` NOT IN ({ids})",
|
||||
"eb_user": "`spread_uid` IN ({ids}) AND `uid` NOT IN ({ids})",
|
||||
}
|
||||
|
||||
DELETE_ORDER = [
|
||||
"wa_order",
|
||||
"wa_merchandise",
|
||||
"wa_selfbonus_log",
|
||||
"wa_sharebonus_log",
|
||||
"wa_coupon_log",
|
||||
"wa_withdraw",
|
||||
"wa_address",
|
||||
"wa_alipay",
|
||||
"wa_bank",
|
||||
"wa_money_log",
|
||||
"eb_ali_pay_info",
|
||||
"eb_article",
|
||||
"eb_sms_record",
|
||||
"eb_store_bargain_user_help",
|
||||
"eb_store_bargain_user",
|
||||
"eb_store_cart",
|
||||
"eb_store_coupon_user",
|
||||
"eb_store_order",
|
||||
"eb_store_pink",
|
||||
"eb_store_product_log",
|
||||
"eb_store_product_relation",
|
||||
"eb_store_product_reply",
|
||||
"eb_system_store_staff",
|
||||
"eb_user_address",
|
||||
"eb_user_bill",
|
||||
"eb_user_brokerage_record",
|
||||
"eb_user_experience_record",
|
||||
"eb_user_extract",
|
||||
"eb_user_integral_record",
|
||||
"eb_user_level",
|
||||
"eb_user_recharge",
|
||||
"eb_user_sign",
|
||||
"eb_user_token",
|
||||
"eb_user_visit_record",
|
||||
"t_platform_account",
|
||||
"eb_user",
|
||||
"wa_users",
|
||||
]
|
||||
|
||||
|
||||
def parse_doc_config() -> dict[str, str]:
|
||||
text = DOC.read_text(encoding="utf-8")
|
||||
|
||||
def grab(name: str) -> str:
|
||||
m = re.search(rf"^\s*{name}:\s*(.+?)\s*$", text, flags=re.M)
|
||||
if not m:
|
||||
raise ValueError(f"missing datasource {name} in {DOC}")
|
||||
return m.group(1).strip()
|
||||
|
||||
return {
|
||||
"host": grab("rds"),
|
||||
"database": grab("name"),
|
||||
"user": grab("username"),
|
||||
"password": grab("password"),
|
||||
}
|
||||
|
||||
|
||||
def connect(config: dict[str, str], cursorclass=None):
|
||||
kwargs = {
|
||||
"host": config["host"],
|
||||
"user": config["user"],
|
||||
"password": config["password"],
|
||||
"database": config["database"],
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"connect_timeout": 10,
|
||||
"read_timeout": 120,
|
||||
"write_timeout": 120,
|
||||
}
|
||||
if cursorclass is not None:
|
||||
kwargs["cursorclass"] = cursorclass
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def ids() -> list[int]:
|
||||
return list(TARGETS)
|
||||
|
||||
|
||||
def ids_sql() -> str:
|
||||
return ",".join(["%s"] * len(TARGETS))
|
||||
|
||||
|
||||
def params_for(condition: str) -> tuple[int, ...]:
|
||||
repeats = condition.count("{ids}")
|
||||
return tuple(ids() * repeats)
|
||||
|
||||
|
||||
def sql_condition(condition: str) -> str:
|
||||
return condition.format(ids=ids_sql())
|
||||
|
||||
|
||||
def table_exists(cur, table: str) -> bool:
|
||||
cur.execute("SHOW TABLES LIKE %s", (table,))
|
||||
return bool(cur.fetchone())
|
||||
|
||||
|
||||
def collect_counts(cur) -> tuple[dict[str, int], dict[str, int]]:
|
||||
delete_counts: dict[str, int] = {}
|
||||
update_counts: dict[str, int] = {}
|
||||
for table in DELETE_ORDER:
|
||||
if not table_exists(cur, table):
|
||||
continue
|
||||
condition = " OR ".join(f"({sql_condition(c)})" for c in DELETE_CONDITIONS[table])
|
||||
params: list[int] = []
|
||||
for c in DELETE_CONDITIONS[table]:
|
||||
params.extend(params_for(c))
|
||||
cur.execute(f"SELECT COUNT(*) FROM `{table}` WHERE {condition}", tuple(params))
|
||||
delete_counts[table] = int(cur.fetchone()[0])
|
||||
for table, condition_template in UPDATE_CONDITIONS.items():
|
||||
if not table_exists(cur, table):
|
||||
continue
|
||||
condition = sql_condition(condition_template)
|
||||
cur.execute(f"SELECT COUNT(*) FROM `{table}` WHERE {condition}", params_for(condition_template))
|
||||
update_counts[table] = int(cur.fetchone()[0])
|
||||
return delete_counts, update_counts
|
||||
|
||||
|
||||
def validate_targets(cur, require_present: bool) -> None:
|
||||
cur.execute("SELECT DATABASE()")
|
||||
database = cur.fetchone()[0]
|
||||
if database != EXPECTED_DATABASE:
|
||||
raise RuntimeError(f"refusing to run against database {database!r}")
|
||||
clause = ids_sql()
|
||||
cur.execute(f"SELECT id,nickname,mobile FROM `wa_users` WHERE `id` IN ({clause})", ids())
|
||||
rows = {int(row[0]): (row[1], str(row[2])) for row in cur.fetchall()}
|
||||
missing = sorted(set(TARGETS) - set(rows))
|
||||
if missing:
|
||||
message = f"target wa_users rows missing: {missing}"
|
||||
if require_present:
|
||||
raise RuntimeError(message)
|
||||
print(f"{message}; treating as already removed in dry-run")
|
||||
mismatched: list[dict[str, Any]] = []
|
||||
for uid, (expected_name, expected_phone) in TARGETS.items():
|
||||
if uid not in rows:
|
||||
continue
|
||||
actual_name, actual_phone = rows[uid]
|
||||
if actual_phone != expected_phone:
|
||||
mismatched.append(
|
||||
{
|
||||
"uid": uid,
|
||||
"expected": [expected_name, expected_phone],
|
||||
"actual": [actual_name, actual_phone],
|
||||
}
|
||||
)
|
||||
if mismatched:
|
||||
raise RuntimeError(f"target user phone mismatch: {json.dumps(mismatched, ensure_ascii=False)}")
|
||||
|
||||
|
||||
def print_counts(title: str, counts: dict[str, int]) -> None:
|
||||
print(title)
|
||||
for table, count in counts.items():
|
||||
if count:
|
||||
print(f" {table}: {count}")
|
||||
if not any(counts.values()):
|
||||
print(" (all zero)")
|
||||
|
||||
|
||||
def backup_rows(config: dict[str, str], backup_path: Path) -> None:
|
||||
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
backup_conn = connect(config, cursorclass=SSCursor)
|
||||
try:
|
||||
with gzip.open(backup_path, "wt", encoding="utf-8") as out:
|
||||
out.write("-- bygsf212 four-user removal backup\n")
|
||||
out.write(f"-- created_at: {datetime.now().isoformat(timespec='seconds')}\n")
|
||||
out.write(f"-- target_ids: {','.join(str(x) for x in ids())}\n")
|
||||
out.write("SET NAMES utf8mb4;\n")
|
||||
tables = list(dict.fromkeys([*DELETE_ORDER, *UPDATE_CONDITIONS.keys()]))
|
||||
for table in tables:
|
||||
if not table_exists(backup_conn.cursor(), table):
|
||||
continue
|
||||
conditions: list[str] = []
|
||||
params: list[int] = []
|
||||
for c in DELETE_CONDITIONS.get(table, []):
|
||||
conditions.append(f"({sql_condition(c)})")
|
||||
params.extend(params_for(c))
|
||||
if table in UPDATE_CONDITIONS:
|
||||
c = UPDATE_CONDITIONS[table]
|
||||
conditions.append(f"({sql_condition(c)})")
|
||||
params.extend(params_for(c))
|
||||
if not conditions:
|
||||
continue
|
||||
where = " OR ".join(conditions)
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SHOW CREATE TABLE `{table}`")
|
||||
create_sql = cur.fetchone()[1]
|
||||
out.write(f"\n-- Table `{table}` affected rows\n")
|
||||
out.write(f"-- Restore manually with INSERT statements below if needed.\n")
|
||||
out.write(create_sql + ";\n")
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SELECT * FROM `{table}` WHERE {where}", tuple(params))
|
||||
batch: list[str] = []
|
||||
row_count = 0
|
||||
for row in cur:
|
||||
batch.append("(" + ",".join(backup_conn.literal(v) for v in row) + ")")
|
||||
row_count += 1
|
||||
if len(batch) >= 200:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
batch = []
|
||||
if batch:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
print(f"backup {table}: rows={row_count}")
|
||||
finally:
|
||||
backup_conn.close()
|
||||
|
||||
|
||||
def execute_cleanup(cur) -> tuple[dict[str, int], dict[str, int]]:
|
||||
updated: dict[str, int] = {}
|
||||
deleted: dict[str, int] = {}
|
||||
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 0")
|
||||
condition = sql_condition(UPDATE_CONDITIONS["wa_users"])
|
||||
cur.execute(f"UPDATE `wa_users` SET `pid` = 0 WHERE {condition}", params_for(UPDATE_CONDITIONS["wa_users"]))
|
||||
updated["wa_users.pid"] = cur.rowcount
|
||||
condition = sql_condition(UPDATE_CONDITIONS["eb_user"])
|
||||
cur.execute(
|
||||
f"UPDATE `eb_user` SET `spread_uid` = 0, `spread_time` = NULL WHERE {condition}",
|
||||
params_for(UPDATE_CONDITIONS["eb_user"]),
|
||||
)
|
||||
updated["eb_user.spread_uid"] = cur.rowcount
|
||||
|
||||
for table in DELETE_ORDER:
|
||||
if not table_exists(cur, table):
|
||||
continue
|
||||
condition = " OR ".join(f"({sql_condition(c)})" for c in DELETE_CONDITIONS[table])
|
||||
params: list[int] = []
|
||||
for c in DELETE_CONDITIONS[table]:
|
||||
params.extend(params_for(c))
|
||||
cur.execute(f"DELETE FROM `{table}` WHERE {condition}", tuple(params))
|
||||
deleted[table] = cur.rowcount
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
return updated, deleted
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--execute", action="store_true", help="perform cleanup and commit")
|
||||
parser.add_argument("--backup-dir", type=Path, default=ROOT / "docs" / "sql" / "backups")
|
||||
args = parser.parse_args()
|
||||
|
||||
config = parse_doc_config()
|
||||
conn = connect(config)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
validate_targets(cur, require_present=args.execute)
|
||||
delete_counts, update_counts = collect_counts(cur)
|
||||
print(f"target_ids={ids()}")
|
||||
print_counts("delete_counts", delete_counts)
|
||||
print_counts("external_reference_update_counts", update_counts)
|
||||
if not args.execute:
|
||||
print("dry_run_only=true")
|
||||
conn.rollback()
|
||||
return 0
|
||||
|
||||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = args.backup_dir / f"bygsf212_remove_93251_93272_93273_93276_before_{stamp}.sql.gz"
|
||||
print(f"backup_path={backup_path}")
|
||||
backup_rows(config, backup_path)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
updated, deleted = execute_cleanup(cur)
|
||||
print("updated_rows")
|
||||
print(json.dumps(updated, ensure_ascii=False, indent=2))
|
||||
print("deleted_rows")
|
||||
print(json.dumps(deleted, ensure_ascii=False, indent=2))
|
||||
after_deletes, after_updates = collect_counts(cur)
|
||||
print_counts("after_delete_counts_before_commit", after_deletes)
|
||||
print_counts("after_update_counts_before_commit", after_updates)
|
||||
conn.commit()
|
||||
print("COMMIT ok")
|
||||
return 0
|
||||
except Exception as exc:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
except Exception:
|
||||
pass
|
||||
conn.rollback()
|
||||
print(f"ROLLBACK: {exc}", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
388
docs/sql/run_com_sqszx202_cleanup.py
Normal file
388
docs/sql/run_com_sqszx202_cleanup.py
Normal file
@@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run data cleanup for docs/com-sqszx202-data-imgration.md.
|
||||
|
||||
Default mode is a read-only dry run. Use --execute to create a local SQL backup,
|
||||
delete rows according to the migration document, and commit the transaction.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pymysql
|
||||
from pymysql.cursors import SSCursor
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DOC = ROOT / "docs" / "com-sqszx202-data-imgration.md"
|
||||
DEFAULT_DUMP = Path("/Users/mac/Works26/miao-july/宿迁盛泽鑫/anpengran-yangtangyoupin_2026-06-14_02-15-02_mysql_data.sql")
|
||||
CUTOFF = "2026-06-12 00:00:00"
|
||||
TABLES = [
|
||||
"wa_order",
|
||||
"wa_withdraw",
|
||||
"eb_store_order",
|
||||
"wa_merchandise",
|
||||
"wa_selfbonus_log",
|
||||
"wa_sharebonus_log",
|
||||
"wa_coupon_log",
|
||||
"eb_user_integral_record",
|
||||
"eb_user",
|
||||
"wa_users",
|
||||
]
|
||||
FILTERS = {
|
||||
"wa_users": "id",
|
||||
"eb_user": "uid",
|
||||
"wa_selfbonus_log": "user_id",
|
||||
"wa_sharebonus_log": "user_id",
|
||||
"wa_coupon_log": "user_id",
|
||||
"eb_user_integral_record": "uid",
|
||||
}
|
||||
CLEAR_TABLES = ["wa_order", "wa_withdraw", "eb_store_order"]
|
||||
|
||||
|
||||
def parse_doc() -> tuple[dict[str, str], list[int]]:
|
||||
text = DOC.read_text(encoding="utf-8")
|
||||
def grab(name: str) -> str:
|
||||
m = re.search(rf"^\s*{name}:\s*(.+?)\s*$", text, flags=re.M)
|
||||
if not m:
|
||||
raise ValueError(f"missing datasource {name} in {DOC}")
|
||||
return m.group(1).strip()
|
||||
|
||||
ids_match = re.search(r"保留名单:\s*\n\s*`([^`]+)`", text)
|
||||
if not ids_match:
|
||||
raise ValueError(f"missing user id keep list in {DOC}")
|
||||
ids = [int(x.strip()) for x in ids_match.group(1).split(",") if x.strip()]
|
||||
if len(ids) != len(set(ids)):
|
||||
raise ValueError("duplicate ids in keep list")
|
||||
config = {
|
||||
"host": grab("rds"),
|
||||
"database": grab("name"),
|
||||
"user": grab("username"),
|
||||
"password": grab("password"),
|
||||
}
|
||||
return config, ids
|
||||
|
||||
|
||||
def split_top_level_tuples(values_blob: str) -> list[str]:
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
n = len(values_blob)
|
||||
while i < n:
|
||||
if values_blob[i] != "(":
|
||||
i += 1
|
||||
continue
|
||||
depth = 0
|
||||
in_quote = False
|
||||
start = i
|
||||
j = i
|
||||
while j < n:
|
||||
c = values_blob[j]
|
||||
if in_quote:
|
||||
if c == "\\":
|
||||
j += 2
|
||||
continue
|
||||
if c == "'":
|
||||
if j + 1 < n and values_blob[j + 1] == "'":
|
||||
j += 2
|
||||
continue
|
||||
in_quote = False
|
||||
j += 1
|
||||
continue
|
||||
if c == "'":
|
||||
in_quote = True
|
||||
elif c == "(":
|
||||
depth += 1
|
||||
elif c == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
out.append(values_blob[start : j + 1])
|
||||
j += 1
|
||||
break
|
||||
j += 1
|
||||
i = j
|
||||
return out
|
||||
|
||||
|
||||
def split_mysql_fields(inner: str) -> list[str]:
|
||||
out: list[str] = []
|
||||
cur: list[str] = []
|
||||
i = 0
|
||||
n = len(inner)
|
||||
while i < n:
|
||||
c = inner[i]
|
||||
if c == "'":
|
||||
cur.append(c)
|
||||
i += 1
|
||||
while i < n:
|
||||
c = inner[i]
|
||||
cur.append(c)
|
||||
if c == "\\":
|
||||
if i + 1 < n:
|
||||
cur.append(inner[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
if c == "'":
|
||||
if i + 1 < n and inner[i + 1] == "'":
|
||||
cur.append(inner[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if c == ",":
|
||||
out.append("".join(cur).strip())
|
||||
cur = []
|
||||
i += 1
|
||||
continue
|
||||
cur.append(c)
|
||||
i += 1
|
||||
out.append("".join(cur).strip())
|
||||
return out
|
||||
|
||||
|
||||
def unquote_sql_string(raw: str) -> str:
|
||||
raw = raw.strip()
|
||||
if raw.startswith("'") and raw.endswith("'"):
|
||||
body = raw[1:-1]
|
||||
body = body.replace("''", "'")
|
||||
body = body.replace("\\'", "'").replace("\\\\", "\\")
|
||||
return body
|
||||
return raw
|
||||
|
||||
|
||||
def extract_wa_merchandise_keep_ids(dump: Path, keep_users: set[int]) -> list[int]:
|
||||
if not dump.is_file():
|
||||
raise FileNotFoundError(f"dump not found: {dump}")
|
||||
keep_ids: set[int] = set()
|
||||
total_rows = 0
|
||||
insert_lines = 0
|
||||
with dump.open("r", encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
if "INSERT INTO `wa_merchandise`" not in line or "VALUES" not in line:
|
||||
continue
|
||||
insert_lines += 1
|
||||
blob = line[line.index("VALUES") + len("VALUES") :].strip()
|
||||
if blob.endswith(";"):
|
||||
blob = blob[:-1].strip()
|
||||
for tup in split_top_level_tuples(blob):
|
||||
total_rows += 1
|
||||
fields = split_mysql_fields(tup.strip()[1:-1])
|
||||
if len(fields) < 10:
|
||||
raise ValueError(f"malformed wa_merchandise tuple: {tup[:120]}")
|
||||
row_id = int(fields[0])
|
||||
user_id = int(fields[2])
|
||||
created_at = unquote_sql_string(fields[8])
|
||||
if created_at >= CUTOFF and user_id in keep_users:
|
||||
keep_ids.add(row_id)
|
||||
if insert_lines == 0:
|
||||
raise ValueError("no INSERT INTO `wa_merchandise` found in dump")
|
||||
print(f"dump_wa_merchandise_rows={total_rows} keep_by_rule={len(keep_ids)}")
|
||||
return sorted(keep_ids)
|
||||
|
||||
|
||||
def connect(config: dict[str, str], cursorclass=None):
|
||||
kwargs = {
|
||||
"host": config["host"],
|
||||
"user": config["user"],
|
||||
"password": config["password"],
|
||||
"database": config["database"],
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"read_timeout": 120,
|
||||
"write_timeout": 120,
|
||||
}
|
||||
if cursorclass is not None:
|
||||
kwargs["cursorclass"] = cursorclass
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def placeholders(items: list[int] | set[int]) -> str:
|
||||
if not items:
|
||||
return "NULL"
|
||||
return ",".join(["%s"] * len(items))
|
||||
|
||||
|
||||
def count_where(cur, table: str, where: str = "1=1", params: tuple[Any, ...] = ()) -> int:
|
||||
cur.execute(f"SELECT COUNT(*) FROM `{table}` WHERE {where}", params)
|
||||
return int(cur.fetchone()[0])
|
||||
|
||||
|
||||
def inspect_schema(cur) -> None:
|
||||
cur.execute("SELECT DATABASE()")
|
||||
database = cur.fetchone()[0]
|
||||
if database != "sqszx202":
|
||||
raise RuntimeError(f"refusing to run against database {database!r}")
|
||||
for table in TABLES:
|
||||
cur.execute("SHOW TABLES LIKE %s", (table,))
|
||||
if not cur.fetchone():
|
||||
raise RuntimeError(f"missing table `{table}`")
|
||||
required = {
|
||||
"wa_users": {"id"},
|
||||
"eb_user": {"uid"},
|
||||
"wa_order": set(),
|
||||
"wa_withdraw": set(),
|
||||
"eb_store_order": set(),
|
||||
"wa_merchandise": {"id", "user_id", "created_at"},
|
||||
"wa_selfbonus_log": {"user_id"},
|
||||
"wa_sharebonus_log": {"user_id"},
|
||||
"wa_coupon_log": {"user_id"},
|
||||
"eb_user_integral_record": {"uid"},
|
||||
}
|
||||
for table, cols in required.items():
|
||||
if not cols:
|
||||
continue
|
||||
cur.execute(f"SHOW COLUMNS FROM `{table}`")
|
||||
actual = {row[0] for row in cur.fetchall()}
|
||||
missing = cols - actual
|
||||
if missing:
|
||||
raise RuntimeError(f"table `{table}` missing columns: {sorted(missing)}")
|
||||
|
||||
|
||||
def collect_counts(cur, keep_users: list[int], keep_merchandise_ids: list[int]) -> dict[str, dict[str, int]]:
|
||||
counts: dict[str, dict[str, int]] = {}
|
||||
user_clause = placeholders(keep_users)
|
||||
merch_clause = placeholders(keep_merchandise_ids)
|
||||
for table in CLEAR_TABLES:
|
||||
total = count_where(cur, table)
|
||||
counts[table] = {"before": total, "keep": 0, "delete": total}
|
||||
for table, col in FILTERS.items():
|
||||
total = count_where(cur, table)
|
||||
keep = count_where(cur, table, f"`{col}` IN ({user_clause})", tuple(keep_users))
|
||||
counts[table] = {"before": total, "keep": keep, "delete": total - keep}
|
||||
total = count_where(cur, "wa_merchandise")
|
||||
keep = (
|
||||
count_where(cur, "wa_merchandise", f"`id` IN ({merch_clause})", tuple(keep_merchandise_ids))
|
||||
if keep_merchandise_ids
|
||||
else 0
|
||||
)
|
||||
counts["wa_merchandise"] = {"before": total, "keep": keep, "delete": total - keep}
|
||||
return counts
|
||||
|
||||
|
||||
def print_counts(title: str, counts: dict[str, dict[str, int]]) -> None:
|
||||
print(title)
|
||||
for table in TABLES:
|
||||
c = counts[table]
|
||||
print(f" {table}: before={c['before']} keep={c['keep']} delete={c['delete']}")
|
||||
|
||||
|
||||
def backup_tables(config: dict[str, str], backup_path: Path) -> None:
|
||||
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
backup_conn = connect(config, cursorclass=SSCursor)
|
||||
try:
|
||||
with gzip.open(backup_path, "wt", encoding="utf-8") as out:
|
||||
out.write("-- sqszx202 cleanup backup\n")
|
||||
out.write(f"-- created_at: {datetime.now().isoformat(timespec='seconds')}\n")
|
||||
out.write("SET NAMES utf8mb4;\n")
|
||||
for table in TABLES:
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SHOW CREATE TABLE `{table}`")
|
||||
row = cur.fetchone()
|
||||
create_sql = row[1]
|
||||
out.write(f"\n-- Table `{table}`\n")
|
||||
out.write(f"DROP TABLE IF EXISTS `{table}`;\n")
|
||||
out.write(create_sql + ";\n")
|
||||
with backup_conn.cursor() as cur:
|
||||
cur.execute(f"SELECT * FROM `{table}`")
|
||||
batch: list[str] = []
|
||||
row_count = 0
|
||||
for row in cur:
|
||||
batch.append("(" + ",".join(backup_conn.literal(v) for v in row) + ")")
|
||||
row_count += 1
|
||||
if len(batch) >= 200:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
batch = []
|
||||
if batch:
|
||||
out.write(f"INSERT INTO `{table}` VALUES\n")
|
||||
out.write(",\n".join(batch) + ";\n")
|
||||
print(f"backup {table}: rows={row_count}")
|
||||
finally:
|
||||
backup_conn.close()
|
||||
|
||||
|
||||
def execute_cleanup(cur, keep_users: list[int], keep_merchandise_ids: list[int]) -> dict[str, int]:
|
||||
user_clause = placeholders(keep_users)
|
||||
merch_clause = placeholders(keep_merchandise_ids)
|
||||
deleted: dict[str, int] = {}
|
||||
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 0")
|
||||
for table in CLEAR_TABLES:
|
||||
cur.execute(f"DELETE FROM `{table}`")
|
||||
deleted[table] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `wa_merchandise` WHERE `id` NOT IN ({merch_clause})", tuple(keep_merchandise_ids))
|
||||
deleted["wa_merchandise"] = cur.rowcount
|
||||
for table in ["wa_selfbonus_log", "wa_sharebonus_log", "wa_coupon_log"]:
|
||||
cur.execute(f"DELETE FROM `{table}` WHERE `user_id` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted[table] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `eb_user_integral_record` WHERE `uid` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted["eb_user_integral_record"] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `eb_user` WHERE `uid` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted["eb_user"] = cur.rowcount
|
||||
cur.execute(f"DELETE FROM `wa_users` WHERE `id` NOT IN ({user_clause})", tuple(keep_users))
|
||||
deleted["wa_users"] = cur.rowcount
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
return deleted
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--execute", action="store_true", help="perform DELETEs and COMMIT")
|
||||
parser.add_argument("--dump", type=Path, default=DEFAULT_DUMP)
|
||||
parser.add_argument("--backup-dir", type=Path, default=ROOT / "docs" / "sql" / "backups")
|
||||
args = parser.parse_args()
|
||||
|
||||
config, keep_users = parse_doc()
|
||||
keep_merchandise_ids = extract_wa_merchandise_keep_ids(args.dump, set(keep_users))
|
||||
print(f"keep_user_ids={len(keep_users)} keep_wa_merchandise_ids={len(keep_merchandise_ids)}")
|
||||
|
||||
conn = connect(config)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
inspect_schema(cur)
|
||||
before = collect_counts(cur, keep_users, keep_merchandise_ids)
|
||||
print_counts("before_counts", before)
|
||||
if not args.execute:
|
||||
print("dry_run_only=true")
|
||||
conn.rollback()
|
||||
return 0
|
||||
|
||||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = args.backup_dir / f"sqszx202_cleanup_before_{stamp}.sql.gz"
|
||||
print(f"backup_path={backup_path}")
|
||||
backup_tables(config, backup_path)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
deleted = execute_cleanup(cur, keep_users, keep_merchandise_ids)
|
||||
after = collect_counts(cur, keep_users, keep_merchandise_ids)
|
||||
print("deleted_rows")
|
||||
print(json.dumps(deleted, ensure_ascii=False, indent=2))
|
||||
print_counts("after_counts_before_commit", after)
|
||||
conn.commit()
|
||||
print("COMMIT ok")
|
||||
|
||||
with conn.cursor() as cur:
|
||||
final_counts = collect_counts(cur, keep_users, keep_merchandise_ids)
|
||||
print_counts("final_counts", final_counts)
|
||||
return 0
|
||||
except Exception as e:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SET FOREIGN_KEY_CHECKS = 1")
|
||||
except Exception:
|
||||
pass
|
||||
conn.rollback()
|
||||
print(f"ROLLBACK: {e}", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
68
outputs/screenshot_process_table/build_process_excel.py
Normal file
68
outputs/screenshot_process_table/build_process_excel.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
|
||||
OUTPUT = "outputs/screenshot_process_table/小程序上线流程表.xlsx"
|
||||
|
||||
headers = ["阶段", "任务", "产出物", "负责人", "建议时长"]
|
||||
rows = [
|
||||
["需求与原型", "需求澄清、功能清单、原型评审", "PRD、原型图", "产品", "2~3天"],
|
||||
["UI设计", "界面设计、切图标注", "设计稿、切图", "UI", "3~4天"],
|
||||
["开发", "前端+后端联调、自测", "代码包", "前端/后端", "6~8天"],
|
||||
["测试与修复", "功能测试、兼容性、回归", "测试报告", "测试", "3~4天"],
|
||||
["小程序提审", "提交代码、填写审核信息", "审核中", "产品/运营", "1~7天(官方不等)"],
|
||||
["发布上线", "审核通过后全量/灰度发布", "线上版本", "产品/运营", "1天"],
|
||||
]
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "流程表"
|
||||
|
||||
ws.append(headers)
|
||||
for row in rows:
|
||||
ws.append(row)
|
||||
|
||||
header_fill = PatternFill("solid", fgColor="F3F6FA")
|
||||
grid = Side(style="thin", color="D9DEE7")
|
||||
border = Border(bottom=grid)
|
||||
|
||||
for cell in ws[1]:
|
||||
cell.font = Font(name="Arial", bold=True, size=12, color="111827")
|
||||
cell.fill = header_fill
|
||||
cell.alignment = Alignment(horizontal="left", vertical="center")
|
||||
cell.border = border
|
||||
|
||||
for row in ws.iter_rows(min_row=2, max_row=ws.max_row):
|
||||
for cell in row:
|
||||
cell.font = Font(name="Arial", size=11, color="111827")
|
||||
cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
|
||||
cell.border = border
|
||||
|
||||
widths = {
|
||||
"A": 16,
|
||||
"B": 34,
|
||||
"C": 22,
|
||||
"D": 16,
|
||||
"E": 22,
|
||||
}
|
||||
|
||||
for col, width in widths.items():
|
||||
ws.column_dimensions[col].width = width
|
||||
|
||||
for row_idx in range(1, ws.max_row + 1):
|
||||
ws.row_dimensions[row_idx].height = 28
|
||||
|
||||
ws.freeze_panes = "A2"
|
||||
ws.auto_filter.ref = f"A1:{get_column_letter(ws.max_column)}{ws.max_row}"
|
||||
|
||||
wb.save(OUTPUT)
|
||||
|
||||
check = load_workbook(OUTPUT)
|
||||
sheet = check["流程表"]
|
||||
assert sheet.max_row == 7
|
||||
assert sheet.max_column == 5
|
||||
assert sheet["A2"].value == "需求与原型"
|
||||
assert sheet["E6"].value == "1~7天(官方不等)"
|
||||
|
||||
print(OUTPUT)
|
||||
@@ -274,10 +274,98 @@
|
||||
}
|
||||
|
||||
// #endif
|
||||
view {
|
||||
view,
|
||||
v-uni-view {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* H5 build emits v-uni-* tags; give them the same base layout as uni-* tags. */
|
||||
v-uni-view,
|
||||
v-uni-scroll-view,
|
||||
v-uni-swiper,
|
||||
v-uni-picker,
|
||||
v-uni-form,
|
||||
v-uni-label {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
v-uni-view[hidden],
|
||||
v-uni-image[hidden],
|
||||
v-uni-scroll-view[hidden],
|
||||
v-uni-swiper[hidden],
|
||||
v-uni-swiper-item[hidden],
|
||||
v-uni-text[hidden],
|
||||
v-uni-button[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
v-uni-text {
|
||||
display: inline;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
v-uni-image {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
v-uni-image > div,
|
||||
v-uni-image > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
v-uni-image > div {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
v-uni-scroll-view {
|
||||
position: relative;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
v-uni-scroll-view[scroll-x],
|
||||
v-uni-scroll-view[scroll-y] {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
v-uni-swiper {
|
||||
position: relative;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
v-uni-swiper-item {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
v-uni-button {
|
||||
position: relative;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
line-height: 2.55555556;
|
||||
border-radius: 5px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bg-color-red {
|
||||
background-color: #E93323;
|
||||
}
|
||||
@@ -300,4 +388,4 @@
|
||||
height: 0;
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -454,7 +454,7 @@ export function uploadFile(filePath, uploadUrl = 'upload/image') {
|
||||
});
|
||||
}
|
||||
|
||||
export function uploadUserImage(filePath, pid, model = 'user') {
|
||||
export function uploadUserImage(filePath, userId, model = 'user') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = uni.getStorageSync('token');
|
||||
uni.uploadFile({
|
||||
@@ -466,7 +466,7 @@ export function uploadUserImage(filePath, pid, model = 'user') {
|
||||
},
|
||||
formData: {
|
||||
model,
|
||||
pid
|
||||
userId
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
@@ -560,4 +560,3 @@ export default {
|
||||
uploadFile,
|
||||
uploadUserImage
|
||||
};
|
||||
|
||||
|
||||
7
single_uniapp22miao/babel.config.js
Normal file
7
single_uniapp22miao/babel.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/cli-plugin-babel/preset'],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator'
|
||||
]
|
||||
}
|
||||
@@ -6,8 +6,8 @@
|
||||
// let domain = 'https://jfanyue.szxingming.com'
|
||||
// let domain = 'https://jf.wenjinhui.com'
|
||||
// let domain = 'https://jjy-jf.fwxgpt.com'
|
||||
// czleilei240 项目
|
||||
let domain = 'https://leilei-jf.czchunfang.com'
|
||||
// sqszx202 项目
|
||||
let domain = 'https://jf.j3s4s5.com'
|
||||
// let domain = 'https://jf.jinyawen.com'
|
||||
// let domain = 'https://jf.hapengran.com'
|
||||
// let domain = 'https://jjy-jf.uj345.com'
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
// HTTP_REQUEST_URL:'',
|
||||
HTTP_REQUEST_URL: domain,
|
||||
// H5商城地址
|
||||
HTTP_H5_URL: 'https://leilei-jf.czchunfang.com',
|
||||
HTTP_H5_URL: 'https://jf.j3s4s5.com',
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
HTTP_REQUEST_URL:domain,
|
||||
|
||||
26958
single_uniapp22miao/package-lock.json
generated
26958
single_uniapp22miao/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,16 +6,30 @@
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/uni-h5": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/uni-stat": "^2.0.2-4080420251103001",
|
||||
"mp-html": "^2.5.0",
|
||||
"vue": "^3.5.24"
|
||||
"sass": "^1.69.5",
|
||||
"sass-loader": "^10.4.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
||||
"@dcloudio/uni-cli-i18n": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/uni-cli-shared": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/uni-i18n": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/uni-migration": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/uni-template-compiler": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/vue-cli-plugin-uni": "^2.0.0",
|
||||
"@dcloudio/vue-cli-plugin-uni": "^2.0.2-4080420251103001",
|
||||
"@dcloudio/webpack-uni-pages-loader": "^2.0.2-4080420251103001",
|
||||
"@vue/cli-service": "^5.0.9"
|
||||
"@vue/cli-plugin-babel": "^4.5.19",
|
||||
"@vue/cli-service": "^4.5.19",
|
||||
"cross-env": "^7.0.3",
|
||||
"vue-loader": "^15.11.1",
|
||||
"webpack": "^4.46.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pdfUrl: '/static/sign_contract_czleilei240.pdf',
|
||||
pdfUrl: '/static/sign_contract_sqszx202.pdf',
|
||||
userId: '',
|
||||
isMobile: false,
|
||||
usePdfJs: false,
|
||||
@@ -436,4 +436,3 @@ export default {
|
||||
box-shadow: 0 8rpx 20rpx rgba(255, 45, 45, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -634,12 +634,13 @@ export default {
|
||||
min-height: 100vh;
|
||||
background-color: #F8F8F8;
|
||||
padding-bottom: 20rpx;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// 顶部橙色头部
|
||||
.header-section {
|
||||
background: linear-gradient(90deg, #FF6900 0%, #F54900 100%);
|
||||
padding: 60rpx 30rpx 60rpx 1px;
|
||||
padding: 60rpx 30rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rpx;
|
||||
@@ -739,14 +740,34 @@ export default {
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.banner-section {
|
||||
padding: 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.banner-swiper {
|
||||
width: 100%;
|
||||
height: 260rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 分类导航
|
||||
.category-scroll {
|
||||
white-space: nowrap; font-size: 32rpx;
|
||||
white-space: nowrap;
|
||||
font-size: 32rpx;
|
||||
background-color: #FFFFFF;
|
||||
height: 108rpx; padding: 10rpx 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
@@ -807,6 +828,7 @@ export default {
|
||||
}
|
||||
|
||||
.goods-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -910,6 +932,7 @@ export default {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
@@ -945,6 +968,7 @@ export default {
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
display: block;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
// background-color: #CCCCCC; // Placeholder
|
||||
@@ -1039,6 +1063,7 @@ export default {
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
display: block;
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
@@ -343,12 +343,12 @@ export default {
|
||||
// window.location.href = 'https://shop.wenjinhui.com/?#/pages/personal/index'
|
||||
//window.location.href = 'https://anyue.szxingming.com/?#/pages/personal/index'
|
||||
// window.location.href = 'https://xiashengjun.com/?#/pages/personal/index'
|
||||
window.location.href = 'https://leilei.czchunfang.com/?#/pages/personal/index'
|
||||
window.location.href = 'https://j3s4s5.com/?#/pages/personal/index'
|
||||
// window.location.href = 'http://shop.bosenyuan.com/?#/pages/personal/index'
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
uni.navigateTo({
|
||||
url: '/pages/web-view/index?url=' + encodeURIComponent('https://leilei.czchunfang.com/?#/pages/personal/index')
|
||||
url: '/pages/web-view/index?url=' + encodeURIComponent('https://j3s4s5.com/?#/pages/personal/index')
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
@@ -921,4 +921,3 @@ export default {
|
||||
line-height: 48rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const url = options && options.url ? decodeURIComponent(options.url) : '/static/sign_contract_czleilei240.pdf'
|
||||
const url = options && options.url ? decodeURIComponent(options.url) : '/static/sign_contract_sqszx202.pdf'
|
||||
this.pdfUrl = url
|
||||
},
|
||||
|
||||
@@ -70,4 +70,3 @@ export default {
|
||||
box-shadow: 0 8rpx 20rpx rgba(255, 45, 45, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -364,7 +364,7 @@ export default {
|
||||
// window.location.href = 'https://shop.wenjinhui.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
// window.location.href = 'https://anyue.szxingming.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
// window.location.href = 'https://xiashengjun.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
window.location.href = 'https://leilei.czchunfang.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
window.location.href = 'https://j3s4s5.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
// window.location.href = 'https://shop.uj345.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
}, 1000)
|
||||
// 返回签名信息给上一页面
|
||||
@@ -498,4 +498,3 @@ export default {
|
||||
.btn.primary { background: #FF2D2D; }
|
||||
.btn-text { color: #fff; font-size: 28rpx; }
|
||||
</style>
|
||||
|
||||
|
||||
36
single_uniapp22miao/postcss.config.js
Normal file
36
single_uniapp22miao/postcss.config.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const postcss = require('postcss')
|
||||
const uniPostcss = require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
|
||||
|
||||
function toVw(value) {
|
||||
return value.replace(/%\?([+-]?\d+(?:\.\d+)?)\?%/g, (match, number) => {
|
||||
const rpx = Number(number)
|
||||
if (!Number.isFinite(rpx)) {
|
||||
return match
|
||||
}
|
||||
const vw = Number((rpx / 7.5).toFixed(6)).toString()
|
||||
return vw === '0' ? '0' : `${vw}vw`
|
||||
})
|
||||
}
|
||||
|
||||
const rpxPlaceholderToVw = postcss.plugin('rpx-placeholder-to-vw', () => {
|
||||
return root => {
|
||||
root.walkDecls(decl => {
|
||||
if (decl.value && decl.value.includes('%?')) {
|
||||
decl.value = toVw(decl.value)
|
||||
}
|
||||
})
|
||||
|
||||
root.walkAtRules(atRule => {
|
||||
if (atRule.params && atRule.params.includes('%?')) {
|
||||
atRule.params = toVw(atRule.params)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
uniPostcss(),
|
||||
rpxPlaceholderToVw()
|
||||
]
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'dinProSemiBold';
|
||||
src: url('static/fonts/D-DIN-PRO-600-SemiBold.otf');
|
||||
src: url('./D-DIN-PRO-600-SemiBold.otf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'dinProRegular';
|
||||
src: url('static/fonts/D-DIN-PRO-400-Regular.otf');
|
||||
src: url('./D-DIN-PRO-400-Regular.otf');
|
||||
}
|
||||
|
||||
.semiBold{
|
||||
@@ -16,4 +16,4 @@
|
||||
.pingFang{
|
||||
font-family: 'PingFang SC, PingFang SC';
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,56 @@
|
||||
(function() {
|
||||
var iframe = document.getElementById('iframe');
|
||||
|
||||
function safeDecode(value) {
|
||||
try {
|
||||
return decodeURIComponent((value || '').replace(/\+/g, ' '));
|
||||
} catch (e) {
|
||||
return value || '';
|
||||
}
|
||||
}
|
||||
|
||||
function buildQuery(params) {
|
||||
var pairs = [];
|
||||
for (var key in params) {
|
||||
if (!Object.prototype.hasOwnProperty.call(params, key)) {
|
||||
continue;
|
||||
}
|
||||
if (params[key] === undefined || params[key] === null || params[key] === '') {
|
||||
continue;
|
||||
}
|
||||
pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
|
||||
}
|
||||
return pairs.join('&');
|
||||
}
|
||||
|
||||
function parseQuery(query, ignoredKey) {
|
||||
var result = {
|
||||
params: {},
|
||||
ignoredValue: ''
|
||||
};
|
||||
if (!query) {
|
||||
return result;
|
||||
}
|
||||
var pairs = query.split('&');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
if (!pairs[i]) {
|
||||
continue;
|
||||
}
|
||||
var pair = pairs[i].split('=');
|
||||
var key = safeDecode(pair[0]);
|
||||
var value = pair.length > 1 ? safeDecode(pair.slice(1).join('=')) : '';
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
if (key === ignoredKey) {
|
||||
result.ignoredValue = result.ignoredValue || value;
|
||||
continue;
|
||||
}
|
||||
result.params[key] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取URL参数
|
||||
function getUrlParams() {
|
||||
var params = {};
|
||||
@@ -51,35 +101,79 @@
|
||||
var pairs = search.split('&');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].split('=');
|
||||
params[decodeURIComponent(pair[0])] = pair[1] ? decodeURIComponent(pair[1]) : '';
|
||||
var key = safeDecode(pair[0]);
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
params[key] = pair.length > 1 ? safeDecode(pair.slice(1).join('=')) : '';
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function normalizeHash(rawHash, username) {
|
||||
var hash = rawHash || '';
|
||||
if (hash.charAt(0) === '#') {
|
||||
hash = hash.substring(1);
|
||||
}
|
||||
if (!hash && !username) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var questionIndex = hash.indexOf('?');
|
||||
var path = questionIndex >= 0 ? hash.substring(0, questionIndex) : hash;
|
||||
var query = questionIndex >= 0 ? hash.substring(questionIndex + 1) : '';
|
||||
var parsed = parseQuery(query, 'username');
|
||||
var finalUsername = username || parsed.ignoredValue;
|
||||
|
||||
if (!path) {
|
||||
path = '/pages/integral/index';
|
||||
}
|
||||
if (finalUsername) {
|
||||
parsed.params.username = finalUsername;
|
||||
}
|
||||
|
||||
var queryString = buildQuery(parsed.params);
|
||||
return '#' + path + (queryString ? '?' + queryString : '');
|
||||
}
|
||||
|
||||
function removeHashParam(rawHash, key) {
|
||||
var hash = rawHash || '';
|
||||
if (hash.charAt(0) === '#') {
|
||||
hash = hash.substring(1);
|
||||
}
|
||||
if (!hash) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var questionIndex = hash.indexOf('?');
|
||||
var path = questionIndex >= 0 ? hash.substring(0, questionIndex) : hash;
|
||||
var query = questionIndex >= 0 ? hash.substring(questionIndex + 1) : '';
|
||||
var parsed = parseQuery(query, key);
|
||||
var queryString = buildQuery(parsed.params);
|
||||
return '#' + path + (queryString ? '?' + queryString : '');
|
||||
}
|
||||
|
||||
function getParentHashFromIframe(iframeHash) {
|
||||
var params = getUrlParams();
|
||||
if (params.username) {
|
||||
return removeHashParam(iframeHash, 'username');
|
||||
}
|
||||
return normalizeHash(iframeHash, '');
|
||||
}
|
||||
|
||||
function updateParentUrl(iframeHash) {
|
||||
var parentHash = getParentHashFromIframe(iframeHash);
|
||||
if (parentHash && window.location.hash !== parentHash) {
|
||||
history.replaceState(null, '', window.location.search + parentHash);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化:根据父页面URL设置iframe的src
|
||||
function initIframeSrc() {
|
||||
var params = getUrlParams();
|
||||
var hash = window.location.hash;
|
||||
var iframeSrc = '/';
|
||||
|
||||
// 如果父页面有hash,传递给iframe
|
||||
if (hash) {
|
||||
iframeSrc = '/' + hash;
|
||||
}
|
||||
|
||||
// 如果有username参数,附加到iframe URL
|
||||
if (params.username) {
|
||||
var separator = iframeSrc.includes('?') ? '&' : (iframeSrc.includes('#') ? (iframeSrc.includes('?') ? '&' : '?') : '?');
|
||||
// 对于hash模式,参数需要放在hash后面
|
||||
if (hash) {
|
||||
iframeSrc = iframeSrc + (iframeSrc.includes('?') ? '&' : '?') + 'username=' + encodeURIComponent(params.username);
|
||||
} else {
|
||||
iframeSrc = '/#/pages/integral/index?username=' + encodeURIComponent(params.username);
|
||||
}
|
||||
}
|
||||
|
||||
iframe.src = iframeSrc;
|
||||
var iframeHash = normalizeHash(window.location.hash, params.username);
|
||||
iframe.src = iframeHash ? '/' + iframeHash : '/';
|
||||
}
|
||||
|
||||
// 监听iframe内部路由变化,同步到父页面URL
|
||||
@@ -90,19 +184,12 @@
|
||||
// 监听iframe的hashchange事件
|
||||
iframeWindow.addEventListener('hashchange', function() {
|
||||
var iframeHash = iframeWindow.location.hash;
|
||||
if (iframeHash && window.location.hash !== iframeHash) {
|
||||
// 保留原有的search参数
|
||||
var currentSearch = window.location.search;
|
||||
history.replaceState(null, '', currentSearch + iframeHash);
|
||||
}
|
||||
updateParentUrl(iframeHash);
|
||||
});
|
||||
|
||||
// 初始同步
|
||||
var iframeHash = iframeWindow.location.hash;
|
||||
if (iframeHash && window.location.hash !== iframeHash) {
|
||||
var currentSearch = window.location.search;
|
||||
history.replaceState(null, '', currentSearch + iframeHash);
|
||||
}
|
||||
updateParentUrl(iframeHash);
|
||||
} catch (e) {
|
||||
console.log('无法访问iframe内容(可能跨域):', e);
|
||||
}
|
||||
@@ -111,11 +198,12 @@
|
||||
// 监听父页面hash变化,同步到iframe
|
||||
window.addEventListener('hashchange', function() {
|
||||
try {
|
||||
var parentHash = window.location.hash;
|
||||
var params = getUrlParams();
|
||||
var parentHash = normalizeHash(window.location.hash, params.username);
|
||||
var iframeHash = iframe.contentWindow.location.hash;
|
||||
|
||||
if (parentHash !== iframeHash) {
|
||||
iframe.contentWindow.location.hash = parentHash;
|
||||
iframe.contentWindow.location.hash = parentHash || '#/pages/integral/index';
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('无法同步hash到iframe:', e);
|
||||
@@ -130,10 +218,7 @@
|
||||
setInterval(function() {
|
||||
try {
|
||||
var iframeHash = iframe.contentWindow.location.hash;
|
||||
if (iframeHash && window.location.hash !== iframeHash) {
|
||||
var currentSearch = window.location.search;
|
||||
history.replaceState(null, '', currentSearch + iframeHash);
|
||||
}
|
||||
updateParentUrl(iframeHash);
|
||||
} catch (e) {}
|
||||
}, 500);
|
||||
};
|
||||
@@ -143,12 +228,8 @@
|
||||
if (window.innerWidth <= 420) {
|
||||
// 小屏幕直接跳转到应用
|
||||
var params = getUrlParams();
|
||||
var targetUrl = '/';
|
||||
if (params.username) {
|
||||
targetUrl = '/#/pages/integral/index?username=' + encodeURIComponent(params.username);
|
||||
} else if (window.location.hash) {
|
||||
targetUrl = '/' + window.location.hash;
|
||||
}
|
||||
var targetHash = normalizeHash(window.location.hash, params.username);
|
||||
var targetUrl = targetHash ? '/' + targetHash : '/';
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
};
|
||||
|
||||
BIN
single_uniapp22miao/static/sign_contract_byhlc112.pdf
Normal file
BIN
single_uniapp22miao/static/sign_contract_byhlc112.pdf
Normal file
Binary file not shown.
BIN
single_uniapp22miao/static/sign_contract_sqszx202.pdf
Normal file
BIN
single_uniapp22miao/static/sign_contract_sqszx202.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user