feat:消费机更新
feat:人员调用钱包部门不调动的bug
This commit is contained in:
1 parent
1c86c227ee
commit
2ca14aa1e0
38 files changed
+1903
-35
No files matched your search
@@ -112,6 +112,12 @@
|
||||
<groupId>com.bsd</groupId>
|
||||
<artifactId>pjcloud-common-log</artifactId>
|
||||
</dependency>
|
||||
<!-- App 安装包上传到宝塔等服务器(SFTP) -->
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
<!--选配: mybatis 依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
|
||||
+38
-1
@@ -8,7 +8,9 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.integrated.dto.appmodule.AppVersionDto;
|
||||
import com.bsd.pjcloud.integrated.entity.app.AppVersionEntity;
|
||||
import com.bsd.pjcloud.integrated.service.AppVersionPackageUploadService;
|
||||
import com.bsd.pjcloud.integrated.service.AppVersionService;
|
||||
import com.bsd.pjcloud.integrated.vo.appmodule.AppPackageUploadVo;
|
||||
import com.bsd.pjcloud.integrated.vo.appmodule.AppVersionVo;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
@@ -18,6 +20,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* app版本控制表
|
||||
@@ -30,12 +33,13 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class AppVersionController {
|
||||
|
||||
private final AppVersionService appVersionService;
|
||||
private final AppVersionPackageUploadService appVersionPackageUploadService;
|
||||
|
||||
/* ************************************* start 设备端 start ************************************* */
|
||||
@Operation(summary = "分页查询", description = "分页查询")
|
||||
@GetMapping("/ad/page")
|
||||
public R<Page<AppVersionVo>> hardPage(@ParameterObject Page page, @ParameterObject AppVersionDto dto) {
|
||||
return R.ok(appVersionService.pageList(page, dto));
|
||||
return R.ok(appVersionService.terminalPageList(page, dto));
|
||||
}
|
||||
/* ************************************* end 设备端 end ************************************* */
|
||||
|
||||
@@ -45,6 +49,16 @@ public class AppVersionController {
|
||||
public R<AppVersionEntity> lastVersion(@ParameterObject AppVersionDto dto) {
|
||||
return R.ok(appVersionService.lastVersion(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门邀请等场景:取安卓最新已发布 APK 整包地址(排除 wgt 热更)
|
||||
*/
|
||||
@Operation(summary = "最新安卓APK整包", description = "平台含安卓且 downloadUrl 以 .apk 结尾的最新已发布版本")
|
||||
@GetMapping("/latest-android-apk")
|
||||
@PreAuthorize("@pms.hasPermission('integrated_appVersion_view') || @pms.hasPermission('sys_dept_edit') || @pms.hasPermission('sys_dept_view')")
|
||||
public R<AppVersionEntity> latestAndroidApk() {
|
||||
return R.ok(appVersionService.latestAndroidApk());
|
||||
}
|
||||
/* ************************************* end app end ************************************* */
|
||||
|
||||
/**
|
||||
@@ -89,6 +103,29 @@ public class AppVersionController {
|
||||
return R.ok(appVersionService.updateById(appVersion));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传安装包到指定服务器目录(SFTP),返回对外下载地址
|
||||
*/
|
||||
@Operation(summary = "上传安装包到远程服务器", description = "SFTP 上传 apk/wgt 等到宝塔等服务器指定目录")
|
||||
@SysLog("上传App安装包到远程服务器")
|
||||
@PostMapping("/upload-package")
|
||||
@PreAuthorize("@pms.hasPermission('integrated_appVersion_add')")
|
||||
public R<AppPackageUploadVo> uploadPackage(@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("host") String host,
|
||||
@RequestParam(value = "port", required = false) Integer port,
|
||||
@RequestParam("username") String username,
|
||||
@RequestParam("password") String password,
|
||||
@RequestParam("remoteDir") String remoteDir,
|
||||
@RequestParam("publicBaseUrl") String publicBaseUrl) {
|
||||
try {
|
||||
return R.ok(appVersionPackageUploadService.uploadBySftp(file, host, port, username, password, remoteDir,
|
||||
publicBaseUrl));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除app版本控制表
|
||||
*/
|
||||
|
||||
+9
@@ -13,4 +13,13 @@ public interface AppVersionMapper extends PjcloudBaseMapper<AppVersionEntity> {
|
||||
|
||||
Page<AppVersionVo> pagList(Page page, @Param("dto") AppVersionDto dto);
|
||||
|
||||
/**
|
||||
* 精确查询指定终端和平台的已发布版本,排序由 SQL 固定为版本码倒序。
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param dto 终端 App 编码与平台参数
|
||||
* @return 终端可见的已发布版本分页
|
||||
*/
|
||||
Page<AppVersionVo> terminalPageList(Page page, @Param("dto") AppVersionDto dto);
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.bsd.pjcloud.integrated.service;
|
||||
|
||||
import com.bsd.pjcloud.integrated.vo.appmodule.AppPackageUploadVo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* App 安装包上传到远程服务器(宝塔等)
|
||||
*/
|
||||
public interface AppVersionPackageUploadService {
|
||||
|
||||
/**
|
||||
* 通过 SFTP 上传安装包,并返回对外可访问的下载地址。
|
||||
*
|
||||
* @param file 安装包文件
|
||||
* @param host 服务器 IP/域名
|
||||
* @param port SFTP 端口,默认 22
|
||||
* @param username 登录用户名
|
||||
* @param password 登录密码
|
||||
* @param remoteDir 远程目录,如 /www/wwwroot/h5.xxx.com/download
|
||||
* @param publicBaseUrl 对外访问前缀,如 https://h5.xxx.com/download
|
||||
*/
|
||||
AppPackageUploadVo uploadBySftp(MultipartFile file, String host, Integer port, String username, String password,
|
||||
String remoteDir, String publicBaseUrl);
|
||||
}
|
||||
+16
@@ -17,4 +17,20 @@ public interface AppVersionService extends IService<AppVersionEntity> {
|
||||
* 获取最新版本
|
||||
*/
|
||||
AppVersionEntity lastVersion(AppVersionDto dto);
|
||||
|
||||
/**
|
||||
* 按终端 App 编码和平台精确查询已发布版本。
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param dto 终端 App 编码与平台参数
|
||||
* @return 版本码倒序的终端版本分页
|
||||
*/
|
||||
Page<AppVersionVo> terminalPageList(Page page, AppVersionDto dto);
|
||||
|
||||
/**
|
||||
* 取安卓平台最新已发布的整包安装地址(downloadUrl 以 .apk 结尾)。
|
||||
*
|
||||
* @return 安卓最新已发布整包版本;没有记录时为 {@code null}
|
||||
*/
|
||||
AppVersionEntity latestAndroidApk();
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package com.bsd.pjcloud.integrated.service.impl;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.ssh.Sftp;
|
||||
import com.bsd.pjcloud.integrated.service.AppVersionPackageUploadService;
|
||||
import com.bsd.pjcloud.integrated.vo.appmodule.AppPackageUploadVo;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.Session;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 将 APK/WGT 等安装包经 SFTP 上传到宝塔等目标机指定目录。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AppVersionPackageUploadServiceImpl implements AppVersionPackageUploadService {
|
||||
|
||||
private static final String TAG = "AppPackageUpload";
|
||||
|
||||
private static final Set<String> ALLOWED_EXT = Set.of("apk", "wgt", "ipa", "zip");
|
||||
|
||||
@Override
|
||||
public AppPackageUploadVo uploadBySftp(MultipartFile file, String host, Integer port, String username,
|
||||
String password, String remoteDir, String publicBaseUrl) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new IllegalArgumentException("请选择要上传的安装包文件");
|
||||
}
|
||||
if (StrUtil.hasBlank(host, username, password, remoteDir, publicBaseUrl)) {
|
||||
throw new IllegalArgumentException("服务器地址、账号、密码、远程目录、对外访问前缀不能为空");
|
||||
}
|
||||
String safeDir = normalizeRemoteDir(remoteDir.trim());
|
||||
String originalName = file.getOriginalFilename();
|
||||
if (StrUtil.isBlank(originalName)) {
|
||||
throw new IllegalArgumentException("无法识别文件名");
|
||||
}
|
||||
String fileName = FileUtil.getName(originalName);
|
||||
String ext = FileUtil.extName(fileName);
|
||||
if (StrUtil.isBlank(ext) || !ALLOWED_EXT.contains(ext.toLowerCase(Locale.ROOT))) {
|
||||
throw new IllegalArgumentException("仅支持上传 apk/wgt/ipa/zip 文件");
|
||||
}
|
||||
int sftpPort = (port == null || port <= 0) ? 22 : port;
|
||||
String remoteFilePath = safeDir + "/" + fileName;
|
||||
String publicUrl = joinUrl(publicBaseUrl.trim(), fileName);
|
||||
|
||||
Session session = null;
|
||||
Sftp sftp = null;
|
||||
try {
|
||||
JSch jsch = new JSch();
|
||||
session = jsch.getSession(username.trim(), host.trim(), sftpPort);
|
||||
session.setPassword(password);
|
||||
session.setConfig("StrictHostKeyChecking", "no");
|
||||
session.connect(15000);
|
||||
sftp = new Sftp(session);
|
||||
try {
|
||||
if (!sftp.exist(safeDir)) {
|
||||
sftp.mkDirs(safeDir);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// 部分环境 exist 不稳定,直接尝试建目录
|
||||
try {
|
||||
sftp.mkDirs(safeDir);
|
||||
}
|
||||
catch (Exception mkdirEx) {
|
||||
log.warn("|{}|创建目录失败(可能已存在):|{}|", TAG, mkdirEx.getMessage());
|
||||
}
|
||||
}
|
||||
try (InputStream in = file.getInputStream()) {
|
||||
boolean ok = sftp.upload(safeDir, fileName, in);
|
||||
if (!ok) {
|
||||
throw new IllegalArgumentException("SFTP 上传返回失败");
|
||||
}
|
||||
}
|
||||
log.info("|{}|上传成功 host:|{}| remote:|{}| size:|{}|", TAG, host, remoteFilePath, file.getSize());
|
||||
return AppPackageUploadVo.builder()
|
||||
.url(publicUrl)
|
||||
.fileName(fileName)
|
||||
.remotePath(remoteFilePath)
|
||||
.build();
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
log.error("|{}|SFTP上传失败 host:|{}| dir:|{}| error:|{}|", TAG, host, safeDir, ex.getMessage(), ex);
|
||||
throw new IllegalArgumentException("上传到服务器失败:" + StrUtil.blankToDefault(ex.getMessage(), "请检查账号、目录与网络"));
|
||||
}
|
||||
finally {
|
||||
if (sftp != null) {
|
||||
try {
|
||||
sftp.close();
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (session != null && session.isConnected()) {
|
||||
session.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizeRemoteDir(String remoteDir) {
|
||||
String dir = remoteDir.replace('\\', '/');
|
||||
if (dir.contains("..")) {
|
||||
throw new IllegalArgumentException("远程目录非法");
|
||||
}
|
||||
if (!dir.startsWith("/")) {
|
||||
dir = "/" + dir;
|
||||
}
|
||||
while (dir.endsWith("/") && dir.length() > 1) {
|
||||
dir = dir.substring(0, dir.length() - 1);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
private static String joinUrl(String base, String fileName) {
|
||||
String b = StrUtil.removeSuffix(base, "/");
|
||||
return b + "/" + fileName;
|
||||
}
|
||||
}
|
||||
+27
@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.integrated.constant.appmodule.AppPlatformEnum;
|
||||
import com.bsd.pjcloud.integrated.constant.appmodule.AppVersionStatusEnum;
|
||||
import com.bsd.pjcloud.integrated.dto.appmodule.AppVersionDto;
|
||||
import com.bsd.pjcloud.integrated.entity.app.AppVersionEntity;
|
||||
@@ -39,4 +40,30 @@ public class AppVersionServiceImpl extends ServiceImpl<AppVersionMapper, AppVers
|
||||
.last("limit 1");
|
||||
return baseMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按终端 App 编码和平台精确查询已发布版本。
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param dto 终端 App 编码与平台参数
|
||||
* @return 版本码倒序的终端版本分页
|
||||
*/
|
||||
@Override
|
||||
public Page<AppVersionVo> terminalPageList(Page page, AppVersionDto dto) {
|
||||
return baseMapper.terminalPageList(page, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取安卓平台最新已发布的整包安装地址(downloadUrl 以 .apk 结尾)。
|
||||
* <p>排除 .wgt 热更补丁,供部门邀请下载二维码使用。</p>
|
||||
*/
|
||||
@Override
|
||||
public AppVersionEntity latestAndroidApk() {
|
||||
return baseMapper.selectOne(new LambdaQueryWrapper<AppVersionEntity>()
|
||||
.eq(AppVersionEntity::getStatus, AppVersionStatusEnum.PUBLISHED.getCode())
|
||||
.apply("FIND_IN_SET({0}, platform) > 0", AppPlatformEnum.ANDROID.getCode())
|
||||
.apply("LOWER(download_url) LIKE '%.apk'")
|
||||
.orderByDesc(AppVersionEntity::getVersionCode)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.bsd.pjcloud.integrated.vo.appmodule;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* App 安装包远程上传结果
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@Schema(description = "App安装包上传结果")
|
||||
public class AppPackageUploadVo {
|
||||
|
||||
@Schema(description = "对外下载地址")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "服务器上的文件名")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "服务器完整路径")
|
||||
private String remotePath;
|
||||
}
|
||||
+16
@@ -42,4 +42,20 @@
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="terminalPageList" resultType="com.bsd.pjcloud.integrated.vo.appmodule.AppVersionVo">
|
||||
<include refid="selectA"/>
|
||||
from app_version a
|
||||
<where>
|
||||
a.del_flag = '0'
|
||||
and a.status = '1'
|
||||
<if test="dto.appUniqueCode != null and dto.appUniqueCode != ''">
|
||||
and a.app_unique_code = #{dto.appUniqueCode}
|
||||
</if>
|
||||
<if test="dto.platform != null and dto.platform != ''">
|
||||
and a.platform = #{dto.platform}
|
||||
</if>
|
||||
</where>
|
||||
order by a.version_code desc, a.create_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Reference in new issue
Block a user