Merge remote-tracking branch 'origin/master'

This commit is contained in:
zjf committed 2026-05-13 12:11:13 +08:00
commit 96256c3366
26 files changed
+452 -181

No files matched your search

@@ -135,12 +135,24 @@ public interface CacheConstants {
String WALLET_DETAIL = "wallet:detail:manage";
// 根据钱包id查询钱包详情
String WALLET_DETAIL_ID = "wallet:detail:id";
// 根据用户id钱包类型查询钱包详情
String WALLET_DETAIL_USER_ID_WALLET_TYPE = "wallet:detail:user_id_wallet_type";
String WALLET_TYPE_DETAIL = "wallet:detail:type";
String WALLET_SETTING_DETAIL = "wallet:detail:setting";
String WALLET_RULE_PRICE_CONFIG_DETAIL = "wallet:detail:rule_price_config";
// 用户消费机的额外价格
String WALLET_USER_DISCOUNTS_LIST = "wallet:detail:user_discounts_list";
// 用户的食堂商户(从视图里查询)
String WALLET_VIEWS_CANTEEN_MCH = "wallet:detail:view_canteen_mch";
/* ************************************* end 钱包相关 end ************************************* */
/* ************************************* start 第三方对接相关 start ************************************* */
@@ -53,159 +53,179 @@ import java.util.Map;
@RequiredArgsConstructor
public class DruidSqlLogFilter extends FilterEventAdapter {
private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
private final PjcloudMybatisProperties properties;
private final PjcloudMybatisProperties properties;
@Override
protected void statementExecuteBefore(StatementProxy statement, String sql) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteBefore(StatementProxy statement, String sql) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteBatchBefore(StatementProxy statement) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteBatchBefore(StatementProxy statement) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
statement.setLastExecuteStartNano();
}
@Override
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
statement.setLastExecuteTimeNano();
}
@Override
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
statement.setLastExecuteTimeNano();
}
@Override
public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException {
// 先调用父类关闭 statement
super.statement_close(chain, statement);
// 支持动态开启
if (!properties.isShowSql()) {
return;
}
@Override
public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException {
// 先调用父类关闭 statement
super.statement_close(chain, statement);
// 支持动态开启
if (!properties.isShowSql()) {
return;
}
// 是否开启调试
if (!log.isInfoEnabled()) {
return;
}
// 是否开启调试
if (!log.isInfoEnabled()) {
return;
}
// 打印可执行的 sql
String sql = statement.getBatchSql();
// sql 为空直接返回
if (StringUtils.isEmpty(sql)) {
return;
}
// 打印可执行的 sql
String sql = statement.getBatchSql();
// sql 为空直接返回
if (StringUtils.isEmpty(sql)) {
return;
}
String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
// 判断表名是配置了匹配过滤
if (CollUtil.isNotEmpty(properties.getSkipTable())) {
List<String> skipTableList = properties.getSkipTable();
// 判断表名是配置了匹配过滤
if (CollUtil.isNotEmpty(properties.getSkipTable())) {
List<String> skipTableList = properties.getSkipTable();
List<String> tableNameList = getTablesBydruid(sql, dbType);
if (tableNameList.stream()
.anyMatch(tableName -> StrUtil.containsAnyIgnoreCase(tableName,
ArrayUtil.toArray(skipTableList, String.class)))) {
return;
}
}
List<String> tableNameList = getTablesBydruid(sql, dbType);
if (tableNameList.stream()
.anyMatch(tableName -> StrUtil.containsAnyIgnoreCase(tableName,
ArrayUtil.toArray(skipTableList, String.class)))) {
return;
}
}
int parametersSize = statement.getParametersSize();
List<Object> parameters = new ArrayList<>(parametersSize);
for (int i = 0; i < parametersSize; ++i) {
// 转换参数,处理 java8 时间
parameters.add(getJdbcParameter(statement.getParameter(i)));
}
String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
printSql(formattedSql, statement);
}
int parametersSize = statement.getParametersSize();
List<Object> parameters = new ArrayList<>(parametersSize);
for (int i = 0; i < parametersSize; ++i) {
// 转换参数,处理 java8 时间
parameters.add(getJdbcParameter(statement.getParameter(i)));
}
String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
printSql(formattedSql, statement);
}
private static Object getJdbcParameter(JdbcParameter jdbcParam) {
if (jdbcParam == null) {
return null;
}
Object value = jdbcParam.getValue();
// 处理 java8 时间
if (value instanceof TemporalAccessor) {
return value.toString();
}
return value;
}
private static Object getJdbcParameter(JdbcParameter jdbcParam) {
if (jdbcParam == null) {
return null;
}
Object value = jdbcParam.getValue();
// 处理 java8 时间
if (value instanceof TemporalAccessor) {
return value.toString();
}
return value;
}
private static void printSql(String sql, StatementProxy statement) {
// 打印 sql
String sqlLogger = "\n\n======= Sql Logger ======================" + "\n{}"
+ "\n======= Sql Execute Time: {} =======\n";
log.info(sqlLogger, sql.trim(), format(statement.getLastExecuteTimeNano()));
}
private static void printSql(String sql, StatementProxy statement) {
// 打印 sql
String sqlLogger = "\n\n======= Sql Logger ======================" + "\n{}"
+ "\n======= Sql Execute Time: {} =======\n";
log.info(sqlLogger, sql.trim(), format(statement.getLastExecuteTimeNano()));
}
/**
* 格式化执行时间,单位为 ms 和 s,保留三位小数
* @param nanos 纳秒
* @return 格式化后的时间
*/
private static String format(long nanos) {
if (nanos < 1) {
return "0ms";
}
double millis = (double) nanos / (1000 * 1000);
// 不够 1 ms,最小单位为 ms
if (millis > 1000) {
return String.format("%.3fs", millis / 1000);
}
else {
return String.format("%.3fms", millis);
}
}
private static void printSqlV2(String sql, StatementProxy statement) {
// 打印 sql 和调用堆栈
StringBuilder sqlLogger = new StringBuilder();
sqlLogger.append("\n\n======= Sql Logger V2 ======================\n");
sqlLogger.append("{}\n"); // SQL语句占位符
sqlLogger.append("======= Sql Execute Time: {} =======\n"); // 执行时间占位符
sqlLogger.append("======= Call Stack ======================\n");
/**
* 从SQL中提取表名(sql中出现的所有表)
* @param sql sql语句
* @param dbType dbType
* @return List<String>
*/
public static List<String> getTablesBydruid(String sql, String dbType) {
List<String> result = new ArrayList<String>();
List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
for (SQLStatement stmt : stmtList) {
// 也可以用更精确的解析器,如MySqlSchemaStatVisitor
SchemaStatVisitor visitor;
// SQL server 数据库特殊处理
if (DbType.sqlserver.name().equalsIgnoreCase(dbType)) {
visitor = new SQLServerSchemaStatVisitor();
}
else {
visitor = new SchemaStatVisitor();
}
stmt.accept(visitor);
Map<TableStat.Name, TableStat> tables = visitor.getTables();
for (TableStat.Name name : tables.keySet()) {
result.add(name.getName());
}
}
return result;
}
// 获取调用堆栈信息
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (int i = 3; i < stackTrace.length; i++) { // 跳过前几个内部调用
if (stackTrace[i].getClassName().contains("com.bsd.pjcloud")) { // 只记录项目相关的调用
sqlLogger.append(" at ").append(stackTrace[i].toString()).append("\n");
}
}
sqlLogger.append("=========================================\n");
log.info(sqlLogger.toString(), sql.trim(), format(statement.getLastExecuteTimeNano()));
}
/**
* 格式化执行时间,单位为 ms 和 s,保留三位小数
*
* @param nanos 纳秒
* @return 格式化后的时间
*/
private static String format(long nanos) {
if (nanos < 1) {
return "0ms";
}
double millis = (double) nanos / (1000 * 1000);
// 不够 1 ms,最小单位为 ms
if (millis > 1000) {
return String.format("%.3fs", millis / 1000);
} else {
return String.format("%.3fms", millis);
}
}
/**
* 从SQL中提取表名(sql中出现的所有表)
*
* @param sql sql语句
* @param dbType dbType
* @return List<String>
*/
public static List<String> getTablesBydruid(String sql, String dbType) {
List<String> result = new ArrayList<String>();
List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
for (SQLStatement stmt : stmtList) {
// 也可以用更精确的解析器,如MySqlSchemaStatVisitor
SchemaStatVisitor visitor;
// SQL server 数据库特殊处理
if (DbType.sqlserver.name().equalsIgnoreCase(dbType)) {
visitor = new SQLServerSchemaStatVisitor();
} else {
visitor = new SchemaStatVisitor();
}
stmt.accept(visitor);
Map<TableStat.Name, TableStat> tables = visitor.getTables();
for (TableStat.Name name : tables.keySet()) {
result.add(name.getName());
}
}
return result;
}
}
@@ -53,7 +53,7 @@ public class HardApiServiceImpl implements HardApiService {
result.setWalletBalance(wallet.getBalance());
result.setWalletIsFreeze(wallet.getIsFreeze());
// 查询用户食堂信息
MchManageVo mchDetail = remoteMchService.getFirstMchByDeptId(user.getSysUser().getDeptId(), List.of(MerchantTypeEnum.CANTEEN)).getData();
MchManageVo mchDetail = remoteMchService.getViewsCanteenMchByDeptId(user.getSysUser().getDeptId()).getData();
if (ObjectUtil.isNotEmpty(mchDetail)) {
result.setCanteenId(mchDetail.getId());
}
@@ -86,7 +86,7 @@ public class HardApiServiceImpl implements HardApiService {
result.setWalletBalance(wallet.getBalance());
result.setWalletIsFreeze(wallet.getIsFreeze());
// 查询用户食堂信息
MchManageVo mchDetail = remoteMchService.getFirstMchByDeptId(user.getSysUser().getDeptId(), List.of(MerchantTypeEnum.CANTEEN)).getData();
MchManageVo mchDetail = remoteMchService.getViewsCanteenMchByDeptId(user.getSysUser().getDeptId()).getData();
if (ObjectUtil.isNotEmpty(mchDetail)) {
result.setCanteenId(mchDetail.getId());
}
@@ -461,7 +461,7 @@ public class HardDoorFlowServiceImpl extends ServiceImpl<HardDoorFlowMapper, Har
private void disposeList(HardDoorFlowVo detail) {
// 用户
List<UserVO> usrList = remoteUserService.getUserDeptByIds(List.of(detail.getUserId())).getData();
List<UserVO> usrList = remoteUserService.getInnerUserDeptByIds(List.of(detail.getUserId())).getData();
if (ObjectUtil.isNotEmpty(usrList)) {
DataUtil.convertUserDept(detail, usrList.get(0));
}
@@ -1110,7 +1110,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
List<MchRuleJoinDtoVo> list = mchRuleJoinService.innerMchRoleList(mch.getId()).getData();
if (ObjectUtil.isEmpty(list)) return extraPrice;
// 查询用户食堂
MchManageVo userMch = remoteMchService.getFirstMchByDeptId(sysUser.getDeptId(), List.of(MerchantTypeEnum.CANTEEN)).getData();
MchManageVo userMch = remoteMchService.getViewsCanteenMchByDeptId(sysUser.getDeptId()).getData();
for (MchRuleJoinDtoVo v : list) {
Long basePrice = v.getBasePrice();
Long multiple = v.getMultiple();
@@ -1762,6 +1762,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
// 购物车
if (dto.getIsFindCarList()) {
for (HardOrderVo v : list) {
if (ObjectUtil.isEmpty(v.getParentCarId())) continue;
List<HardCarEntity> carList = hardCarService.getByParentCarId(v.getParentCarId());
List<HardCarVo> carVoList = BeanUtils.toBean(carList, HardCarVo.class);
v.setCarList(carVoList);
@@ -444,9 +444,11 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
}
// 商户
MchManageVo mchDetail = remoteMchService.getById(totalList.getMerchantId()).getData();
if (ObjectUtil.isNotEmpty(mchDetail)) {
WalletDataUtil.convertMch(totalList, mchDetail);
if (ObjectUtil.isNotEmpty(totalList.getMerchantId())) {
MchManageVo mchDetail = remoteMchService.getById(totalList.getMerchantId()).getData();
if (ObjectUtil.isNotEmpty(mchDetail)) {
WalletDataUtil.convertMch(totalList, mchDetail);
}
}
return totalList;
}
@@ -265,7 +265,7 @@ public class SysUser extends Model<SysUser> implements Serializable {
@Schema(description = "编制类型")
private String staffingType;
@Schema(description = "性别")
private Integer gender;
// @Schema(description = "性别")
// private Integer gender;
}
@@ -90,6 +90,10 @@ public interface RemoteUserService {
@PostMapping("/user/inner/list")
R<List<SysUser>> getInnerUserById(@RequestBody List<Long> userIds);
@NoToken
@PostMapping("/user/inner/user-dept")
R<List<UserVO>> getInnerUserDeptByIds(@RequestBody List<Long> userIds);
@PostMapping("/user/user-dept")
R<List<UserVO>> getUserDeptByIds(@RequestBody List<Long> userIds);
@@ -197,6 +201,7 @@ public interface RemoteUserService {
/**
* 根据角色编码查询用户列表
*
* @param code
* @return
*/
@@ -476,6 +476,13 @@ public class SysUserController {
return R.ok(userService.list(Wrappers.<SysUser>lambdaQuery().in(SysUser::getUserId, userIds)));
}
@Inner
@PostMapping("/inner/user-dept")
public R<List<UserVO>> getInnerUserDeptByIds(@RequestBody List<Long> userIds) {
return R.ok(userService.getUserDeptByIds(userIds));
}
@PostMapping("/user-dept")
public R<List<UserVO>> getUserDeptByIds(@RequestBody List<Long> userIds) {
return R.ok(userService.getUserDeptByIds(userIds));
@@ -239,4 +239,7 @@ public interface SysUserService extends IService<SysUser> {
* @return
*/
List<SimpleUserListVO> listUserIdByDeptId(Long deptId, String name);
void deleteDiscountsCacheByUserId(Long userId);
}
@@ -38,6 +38,7 @@ import com.bsd.pjcloud.admin.api.vo.DeptExcelVO;
import com.bsd.pjcloud.admin.api.vo.OrgTreeVO;
import com.bsd.pjcloud.admin.mapper.*;
import com.bsd.pjcloud.admin.service.SysDeptService;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.excel.vo.ErrorMessage;
@@ -46,6 +47,7 @@ import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.wallet.api.feign.RemoteWalletService;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
@@ -112,9 +114,15 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
* 修改部门
*/
@Override
public Boolean edit(SysDept sysDept) {
boolean isEdit = updateById(sysDept);
remoteWalletService.deptChange(sysDept.getDeptId(), Boolean.FALSE);
@CacheEvict(
value = CacheConstants.WALLET_VIEWS_CANTEEN_MCH,
key = "#dto.getDeptId()",
condition = "#dto.getDeptId() != null",
beforeInvocation = true
)
public Boolean edit(SysDept dto) {
boolean isEdit = updateById(dto);
remoteWalletService.deptChange(dto.getDeptId(), Boolean.FALSE);
return isEdit;
}
@@ -61,6 +61,7 @@ import com.bsd.pjcloud.common.core.util.MsgUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.resolver.ParamResolver;
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
import com.bsd.pjcloud.common.excel.vo.ErrorMessage;
import com.bsd.pjcloud.common.security.service.PjcloudUser;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
@@ -75,10 +76,15 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.PictureData;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -90,6 +96,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -116,6 +123,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
private final SysDeptService sysDeptService;
private final StringRedisTemplate stringRedisTemplate;
@Lazy
private final SysUserFaceService sysUserFaceService;
private final SysUserFaceMapper sysUserFaceMapper;
@@ -372,6 +381,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
sysUser.setNickname(userDto.getNickname());
sysUser.setName(userDto.getName());
sysUser.setEmail(userDto.getEmail());
deleteDiscountsCacheByUserId(userDto.getUserId());
return R.ok(this.updateById(sysUser));
}
@@ -425,6 +435,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
remoteWalletService.createWallet(userDto.getUserId());
remoteWalletService.userChange(userDto.getUserId(), Boolean.FALSE);
});
deleteDiscountsCacheByUserId(userDto.getUserId());
return Boolean.TRUE;
}
@@ -993,6 +1005,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
if (isConsent) {
// 创建用户默认钱包
remoteWalletService.createWallet(detail.getUserId());
deleteDiscountsCacheByUserId(detail.getUserId());
}
} catch (Exception e) {
e.printStackTrace();
@@ -1056,4 +1069,24 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
}
return count > 0;
}
@Override
public void deleteDiscountsCacheByUserId(Long userId) {
if (ObjectUtil.isEmpty(userId)) return;
String pattern = TenantContextHolder.getTenantId() + ":" + CacheConstants.WALLET_USER_DISCOUNTS_LIST + "::" + userId + ":*";
Set<String> keys = new HashSet<>();
stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
Cursor<byte[]> cursor = connection.scan(
ScanOptions.scanOptions().match(pattern).count(1000).build()
);
while (cursor.hasNext()) {
keys.add(new String(cursor.next(), StandardCharsets.UTF_8));
}
return null;
});
if (!keys.isEmpty()) {
stringRedisTemplate.delete(keys);
}
}
}
@@ -230,6 +230,7 @@ public class SysUserTransactionServiceImpl extends ServiceImpl<SysUserTransactio
RequestContextHolder.setRequestAttributes(requestAttributes);
remoteWalletService.userChange(detail.getUserId(), Boolean.FALSE);
});
sysUserService.deleteDiscountsCacheByUserId(detail.getUserId());
}
} catch (Exception e) {
e.printStackTrace();
@@ -52,4 +52,8 @@ public interface RemoteMchService {
@NoToken
@GetMapping("/mch-manage/get-first-mch-by-dept-id")
R<MchManageVo> getFirstMchByDeptId(@RequestParam Long deptId, @RequestParam List<MerchantTypeEnum> mchType);
@NoToken
@GetMapping("/mch-manage/get-views-canteen-mch-by-dept-id")
R<MchManageVo> getViewsCanteenMchByDeptId(@RequestParam Long deptId);
}
@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.datascope.DataScopeFuncEnum;
@@ -23,6 +24,7 @@ import com.bsd.pjcloud.wallet.mapper.WalletManageMapper;
import com.bsd.pjcloud.wallet.service.WalletCodeUseService;
import com.bsd.pjcloud.wallet.service.WalletManageService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springdoc.core.annotations.ParameterObject;
@@ -91,6 +93,12 @@ public class WalletManageController {
@Inner
@GetMapping("/get-wallet")
// @Cacheable(
// value = CacheConstants.WALLET_DETAIL_USER_ID_WALLET_TYPE + "#3600",
// key = "#userId + ':' + #walletTypeCode.getCode()",
// condition = "#userId != null && #walletTypeCode.getCode() != null",
// unless = "#result == null || #result.data == null"
// )
public R getWallet(@RequestParam Long userId, @RequestParam WalletTypeEnum walletTypeCode) {
WalletManageEntity wallet = walletManageService.getWallet(userId, walletTypeCode);
if (ObjectUtil.isEmpty(wallet)) return R.failed("用户钱包不存在");
@@ -99,6 +107,12 @@ public class WalletManageController {
@Inner
@GetMapping("/get-wallet-by-id")
// @Cacheable(
// value = CacheConstants.WALLET_DETAIL_ID + "#3600",
// key = "#walletId",
// condition = "#walletId != null",
// unless = "#result == null || #result.data != null"
// )
public R<WalletManageVo> getWalletById(@RequestParam Long walletId) {
WalletManageVo wallet = walletManageMapper.join(walletId);
if (ObjectUtil.isEmpty(wallet)) return R.failed("用户钱包不存在");
@@ -1,5 +1,6 @@
package com.bsd.pjcloud.wallet.controller;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.common.security.annotation.Inner;
@@ -8,6 +9,7 @@ import com.bsd.pjcloud.wallet.api.vo.WalletSettingVo;
import com.bsd.pjcloud.wallet.api.vo.WalletXfjDiscounts;
import com.bsd.pjcloud.wallet.service.WalletSettingService;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -56,7 +58,14 @@ public class WalletSettingController {
/* ************************************* end app end ************************************* */
/* ************************************* start android start ************************************* */
@GetMapping("/ad/get-discounts-list")
@Cacheable(
value = CacheConstants.WALLET_USER_DISCOUNTS_LIST,
key = "#userId + ':' + #hardId",
condition = "#userId != null && #hardId != null",
unless = "#result == null || #result.data == null"
)
public R<List<WalletXfjDiscounts>> androidGetDiscountsList(@RequestParam String hardId, @RequestParam Long userId) {
return R.ok(walletSettingService.androidGetDiscountsList(hardId, userId));
}
@@ -10,6 +10,7 @@ import com.bsd.pjcloud.admin.api.dto.UserInfo;
import com.bsd.pjcloud.admin.api.entity.SysDept;
import com.bsd.pjcloud.admin.api.feign.RemoteDeptService;
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum;
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
import com.bsd.pjcloud.common.core.util.R;
@@ -23,6 +24,7 @@ import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
import com.bsd.pjcloud.wallet.api.entity.mch.MchManageEntity;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
import com.bsd.pjcloud.wallet.service.MchManageService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.access.prepost.PreAuthorize;
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -126,6 +128,15 @@ public class MchManageController {
if (ObjectUtil.isEmpty(mchManage)) return R.failed("商户不存在");
return R.ok(mchManage);
}
@Inner
@GetMapping("/get-views-canteen-mch-by-dept-id")
public R<MchManageVo> getViewsCanteenMchByDeptId(@RequestParam Long deptId) {
MchManageVo mchManage = mchManageService.getViewsCanteenMchByDeptId(deptId);
if (ObjectUtil.isEmpty(mchManage)) return R.failed("商户不存在");
return R.ok(mchManage);
}
/* ************************************* end 远程调用 end ************************************* */
/* ************************************* start app start ************************************* */
@@ -14,6 +14,6 @@ public interface MchDeptViewMapper {
List<WalletMchDeptViewVo> selectList(@Param("dto") WalletMchDeptViewDto dto);
WalletMchDeptViewVo selectByUserId(@Param("userId") Long userId);
WalletMchDeptViewVo selectByDeptId(@Param("deptId") Long deptId);
}
@@ -2,12 +2,15 @@ package com.bsd.pjcloud.wallet.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.security.service.PjcloudUser;
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
import com.bsd.pjcloud.wallet.api.entity.mch.MchManageEntity;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import java.util.ArrayList;
import java.util.List;
@@ -41,6 +44,11 @@ public interface MchManageService extends IService<MchManageEntity> {
MchManageVo getFirstMchByDeptId(Long deptId, List<MerchantTypeEnum> mchTypeList);
/**
* 从视图里获取食堂商户信息
*/
MchManageVo getViewsCanteenMchByDeptId(Long deptId);
/**
* 刷新商户二维码
*/
@@ -121,4 +121,9 @@ public interface WalletManageService extends IService<WalletManageEntity> {
* 是够能够使用智能柜
*/
String isUseDoor(Long userId);
/**
* 删除钱包缓存
*/
void delWalletCache(WalletManageVo wallet);
}
@@ -29,6 +29,7 @@ import com.bsd.pjcloud.common.core.util.CollectionUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.resolver.ParamResolver;
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
import com.bsd.pjcloud.wallet.api.constant.WalletTypeEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletManageBalanceDto;
import com.bsd.pjcloud.wallet.api.dto.WalletManageDto;
@@ -79,6 +80,7 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
@Lazy
private final WalletThirdZyService walletThirdZyService;
private final MchManageService mchManageService;
private final StringRedisTemplate stringRedisTemplate;
private final RemoteUserService userService;
private final RemoteDeptService deptService;
@@ -102,7 +104,7 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
log.info("|{}|创建用户电子钱包失败,userId:{},用户id不存在", TAG, userId);
return;
}
String role = Arrays.stream(user.getRoleCodes()).filter(v -> ObjectUtil.equals(v, autoWalletRoleCode)).findFirst().get();
String role = Arrays.stream(user.getRoleCodes()).filter(v -> ObjectUtil.equals(v, autoWalletRoleCode)).findFirst().orElse(null);
if (ObjectUtil.isEmpty(role)) {
log.info("|{}|创建用户电子钱包失败,userId:{},没有{}权限", TAG, userId, "WALLET_AUTO_CREATE");
return;
@@ -548,6 +550,23 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
return "消费钱包余额不足100元";
}
/**
* 删除钱包缓存
*/
@Override
public void delWalletCache(WalletManageVo wallet) {
if (ObjectUtil.isEmpty(wallet)) return;
Long tenantId = TenantContextHolder.getTenantId();
if (ObjectUtil.isNotEmpty(wallet.getId())) {
String redisKey = tenantId + ":" + CacheConstants.WALLET_DETAIL_ID + "::" + wallet.getId();
stringRedisTemplate.delete(redisKey);
}
if (ObjectUtil.isNotEmpty(wallet.getUserId()) && ObjectUtil.isNotEmpty(wallet.getWalletTypeUniqueCode())) {
String redisKey = tenantId + ":" + CacheConstants.WALLET_DETAIL_USER_ID_WALLET_TYPE + "::" + wallet.getUserId() + ":" + wallet.getWalletTypeUniqueCode();
stringRedisTemplate.delete(redisKey);
}
}
private void disposeList(List<WalletManageVo> list) {
if (ObjectUtil.isNotEmpty(list)) {
// 用户
@@ -6,9 +6,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.dto.UserInfo;
import com.bsd.pjcloud.admin.api.entity.SysUser;
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
import com.bsd.pjcloud.common.core.util.BeanUtils;
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
import com.bsd.pjcloud.wallet.api.constant.WalletXfjDiscountsEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletSettingDto;
import com.bsd.pjcloud.wallet.api.entity.WalletSettingEntity;
@@ -23,12 +24,19 @@ import com.bsd.pjcloud.wallet.service.WalletSettingService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 支付设置
@@ -40,6 +48,7 @@ import java.util.List;
public class WalletSettingServiceImpl extends ServiceImpl<WalletSettingMapper, WalletSettingEntity> implements WalletSettingService {
private final MchManageService mchManageService;
private final StringRedisTemplate stringRedisTemplate;
private final RemoteUserService remoteUserService;
@@ -60,7 +69,7 @@ public class WalletSettingServiceImpl extends ServiceImpl<WalletSettingMapper, W
SysUser sysUser = userInfo.getSysUser();
// 在自营食堂
MchManageVo userMch = mchManageService.getFirstMchByDeptId(sysUser.getDeptId(), List.of(MerchantTypeEnum.CANTEEN));
MchManageVo userMch = mchManageService.getViewsCanteenMchByDeptId(sysUser.getDeptId());
boolean isSelfCatering = ObjectUtil.isNotEmpty(userMch);
List<WalletSettingEntity.WalletXfjDiscounts> list = new ArrayList<>();
@@ -86,6 +95,7 @@ public class WalletSettingServiceImpl extends ServiceImpl<WalletSettingMapper, W
list = setting.getFzbDefaultXfjDiscounts();
}
}
list = list.stream().filter(item -> ObjectUtil.isNotEmpty(item.getEnable()) && ObjectUtil.equals(YesNoEnum.YES.getCode(), item.getEnable())).toList();
if (ObjectUtil.isNotEmpty(list)) {
discountsList.addAll(
list.stream().map(item -> {
@@ -124,6 +134,24 @@ public class WalletSettingServiceImpl extends ServiceImpl<WalletSettingMapper, W
}
WalletSettingEntity updateObj = BeanUtils.toBean(dto, WalletSettingEntity.class);
baseMapper.updateById(updateObj);
deleteAllDiscountsCacheByUserId();
}
public void deleteAllDiscountsCacheByUserId() {
String pattern = TenantContextHolder.getTenantId() + ":" + CacheConstants.WALLET_USER_DISCOUNTS_LIST + "::" + "*";
Set<String> keys = new HashSet<>();
stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
Cursor<byte[]> cursor = connection.scan(
ScanOptions.scanOptions().match(pattern).count(1000).build()
);
while (cursor.hasNext()) {
keys.add(new String(cursor.next(), StandardCharsets.UTF_8));
}
return null;
});
if (!keys.isEmpty()) {
stringRedisTemplate.delete(keys);
}
}
@Data
@@ -36,12 +36,16 @@ import com.bsd.pjcloud.wallet.api.entity.WalletTypeEntity;
import com.bsd.pjcloud.wallet.api.utils.WalletDataUtil;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
import com.bsd.pjcloud.wallet.api.vo.code.MchCollectionCodeVo;
import com.bsd.pjcloud.wallet.api.vo.mch.WalletMchDeptViewVo;
import com.bsd.pjcloud.wallet.mapper.MchManageMapper;
import com.bsd.pjcloud.wallet.mapper.WalletTypeMapper;
import com.bsd.pjcloud.wallet.mapper.mch.view.MchDeptViewMapper;
import com.bsd.pjcloud.wallet.service.MchManageService;
import com.bsd.pjcloud.wallet.service.WalletThirdZyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mock.web.MockMultipartFile;
@@ -82,6 +86,7 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
@Lazy
private final WalletThirdZyService walletThirdZyService;
private final WalletTypeMapper walletTypeMapper;
private final MchDeptViewMapper mchDeptViewMapper;
private final StringRedisTemplate redisTemplate;
@@ -121,6 +126,12 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
@Override
// @CacheEvict(value = CacheConstants.WALLET_MCH, key = "#dto.getId()", condition = "#dto.getId() != null")
@CacheEvict(
value = CacheConstants.WALLET_VIEWS_CANTEEN_MCH,
key = "#dto.getDeptId()",
condition = "#dto.getDeptId() != null",
beforeInvocation = true
)
public void edit(MchManageEntity dto) {
if (existName(dto.getName(), dto.getTypes(), dto.getId())) {
throw new RuntimeException("商户名不能重复");
@@ -311,6 +322,24 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
return getFirstMch(deptIds, mchTypeList);
}
/**
* 从视图里获取食堂商户信息
*/
@Override
@Cacheable(
value = CacheConstants.WALLET_VIEWS_CANTEEN_MCH + "#3600",
key = "#deptId",
condition = "#deptId != null",
unless = "#result == null"
)
public MchManageVo getViewsCanteenMchByDeptId(Long deptId) {
WalletMchDeptViewVo walletMchDeptView = mchDeptViewMapper.selectByDeptId(deptId);
if (ObjectUtil.isEmpty(walletMchDeptView) || ObjectUtil.isEmpty(walletMchDeptView.getDeptId())) {
return null;
}
return baseMapper.join(walletMchDeptView.getMchId());
}
/**
* 刷新商户二维码
*/
@@ -67,12 +67,12 @@ import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.net.URLConnection;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
@@ -415,42 +415,86 @@ public class WalletRechargeInvoiceServiceImpl extends ServiceImpl<WalletRecharge
if (ObjectUtil.isEmpty(imageBaseUrl)) {
throw new RuntimeException("请配置图片基础路径");
}
if (ObjectUtil.isEmpty(invoice.getPicUrl())) {
log.error("|{}|盖章失败,发票图片为空,invoice:|{}|", TAG, JSONUtil.toJsonStr(invoice));
return null;
}
// 开始盖章
String picUrl = String.format("%s%s", imageBaseUrl, invoice.getPicUrl());
// 发票底图宽高 3969 x 2154
int baseMapWidth = 3969, baseMapHeight = 2154;
// 盖章图片宽高
int stampOrgWidth = 600, stampOrgHeight = 600;
String picUrl = invoice.getPicUrl();
if (!picUrl.startsWith("http://") && !picUrl.startsWith("https://")) {
if (imageBaseUrl.endsWith("/") && picUrl.startsWith("/")) {
picUrl = imageBaseUrl + picUrl.substring(1);
} else if (!imageBaseUrl.endsWith("/") && !picUrl.startsWith("/")) {
picUrl = imageBaseUrl + "/" + picUrl;
} else {
picUrl = imageBaseUrl + picUrl;
}
}
int stampWidth = 500, stampHeight = 500;
try {
// 读取底图
BufferedImage baseImage = ImageIO.read(new URL(picUrl));
URLConnection connection = new URL(picUrl).openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
BufferedImage baseImage;
try (InputStream inputStream = connection.getInputStream()) {
baseImage = ImageIO.read(inputStream);
}
if (ObjectUtil.isEmpty(baseImage)) {
log.error("|{}|盖章失败,底图解析失败,picUrl:|{}|invoice:|{}|", TAG, picUrl, JSONUtil.toJsonStr(invoice));
return null;
}
// 读取盖章图片
BufferedImage stampImage = ImageIO.read(ResourceUtil.getResource("image/receiptStamp.png"));
stampImage.getScaledInstance(stampWidth, stampHeight, Image.SCALE_SMOOTH);
BufferedImage stampImage;
try (InputStream inputStream = ResourceUtil.getStream("image/receiptStamp.png")) {
stampImage = ImageIO.read(inputStream);
}
if (ObjectUtil.isEmpty(stampImage)) {
log.error("|{}|盖章失败,盖章图片资源不存在,invoice:|{}|", TAG, JSONUtil.toJsonStr(invoice));
return null;
}
BufferedImage scaledStamp = new BufferedImage(stampWidth, stampHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D stampG2d = scaledStamp.createGraphics();
stampG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
stampG2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
stampG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
stampG2d.drawImage(stampImage, 0, 0, stampWidth, stampHeight, null);
stampG2d.dispose();
// 创建新的图像上下文
Graphics2D g2d = baseImage.createGraphics();
// 设置抗锯齿
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
int baseMapWidth = baseImage.getWidth();
int baseMapHeight = baseImage.getHeight();
int x = (int) (stampWidth + baseMapWidth * 0.15 + 80);
int y = baseMapHeight - stampHeight - 50;
g2d.drawImage(stampImage, x, y, stampWidth, stampHeight, null);
x = Math.max(0, Math.min(x, baseMapWidth - stampWidth));
y = Math.max(0, Math.min(y, baseMapHeight - stampHeight));
g2d.drawImage(scaledStamp, x, y, null);
g2d.dispose();
String newPicUrl = invoice.getPicUrl().replaceFirst("(\\.\\w+)$", "_stamped$1");
File tempFile = File.createTempFile(newPicUrl, ".png");
ImageIO.write(baseImage, "PNG", tempFile);
// 保存到本地用于测试
// ImageIO.write(baseImage, "PNG", new File("test.png"));
// 上传图片
MultipartFile multipartFile = new MockMultipartFile("file", newPicUrl, "image/png", new FileInputStream(tempFile));
R upload = remoteFileService.innerUpload(multipartFile, null, null, null);
Map data = (Map) upload.getData();
String url = (String) data.get("url");
return url;
} catch (IOException e) {
e.printStackTrace();
log.error("|{}|盖章失败,IO异常,invoice:|{}|error:|{}|", TAG, JSONUtil.toJsonStr(invoice), e.getMessage());
String fileName = "invoice_stamped_" + invoice.getId() + ".png";
byte[] imageBytes;
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ImageIO.write(baseImage, "PNG", outputStream);
imageBytes = outputStream.toByteArray();
}
MultipartFile multipartFile = new MockMultipartFile("file", fileName, "image/png", imageBytes);
R<?> upload = remoteFileService.innerUpload(multipartFile, null, null, null);
if (ObjectUtil.isEmpty(upload) || !upload.isOk() || ObjectUtil.isEmpty(upload.getData())) {
log.error("|{}|盖章失败,上传失败,upload:|{}|invoice:|{}|", TAG, JSON.toJSONString(upload), JSONUtil.toJsonStr(invoice));
return null;
}
Object dataObj = upload.getData();
if (!(dataObj instanceof Map<?, ?> data)) {
log.error("|{}|盖章失败,上传返回结构异常,data:|{}|invoice:|{}|", TAG, JSON.toJSONString(dataObj), JSONUtil.toJsonStr(invoice));
return null;
}
Object urlObj = data.get("url");
return ObjectUtil.isEmpty(urlObj) ? null : String.valueOf(urlObj);
} catch (Exception e) {
log.error("|{}|盖章失败,异常,invoice:|{}|", TAG, JSONUtil.toJsonStr(invoice), e);
return null;
}
}
@@ -582,4 +626,4 @@ public class WalletRechargeInvoiceServiceImpl extends ServiceImpl<WalletRecharge
}
}
}
}
}
@@ -52,6 +52,14 @@
order by a.mchDeptId, a.level
</select>
<select id="selectByUserId" resultType="com.bsd.pjcloud.wallet.api.vo.mch.WalletMchDeptViewVo">
<select id="selectByDeptId" resultType="com.bsd.pjcloud.wallet.api.vo.mch.WalletMchDeptViewVo">
<include refid="selectA"/>
from v_mch_dept_tree a
<where>
and a.deptId = #{deptId}
</where>
order by a.level asc
limit 1
</select>
</mapper>
</mapper>
@@ -22,7 +22,7 @@ public class InvoiceTest {
@Test
@Transactional
public void test() {
WalletRechargeInvoiceEntity detail = invoiceService.getById(1937849346822692865L);
WalletRechargeInvoiceEntity detail = invoiceService.getById(2054386187932225537L);
if (ObjectUtil.isNotEmpty(detail)) {
invoiceService.stamp(detail);
}