feat: 重构模块

This commit is contained in:
ageerle
2025-04-10 17:25:23 +08:00
parent 3be9005f95
commit 2509099146
653 changed files with 1000 additions and 165766 deletions

View File

@@ -0,0 +1,77 @@
package org.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 配置信息对象 chat_config
*
* @author ageerle
* @date 2025-04-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_config")
public class ChatConfig extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 配置类型
*/
private String category;
/**
* 配置名称
*/
private String configName;
/**
* 配置值
*/
private String configValue;
/**
* 说明
*/
private String configDict;
/**
* 备注
*/
private String remark;
/**
* 版本
*/
@Version
private Long version;
/**
* 删除标志0代表存在 1代表删除
*/
@TableLogic
private String delFlag;
/**
* 更新IP
*/
private String updateIp;
}

View File

@@ -0,0 +1,67 @@
package org.ruoyi.system.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import org.ruoyi.system.domain.ChatConfig;
/**
* 配置信息业务对象 chat_config
*
* @author ageerle
* @date 2025-04-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatConfig.class, reverseConvertGenerate = false)
public class ChatConfigBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 配置类型
*/
@NotBlank(message = "配置类型不能为空", groups = { AddGroup.class, EditGroup.class })
private String category;
/**
* 配置名称
*/
@NotBlank(message = "配置名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String configName;
/**
* 配置值
*/
@NotBlank(message = "配置值不能为空", groups = { AddGroup.class, EditGroup.class })
private String configValue;
/**
* 说明
*/
@NotBlank(message = "说明不能为空", groups = { AddGroup.class, EditGroup.class })
private String configDict;
/**
* 备注
*/
@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
private String remark;
/**
* 更新IP
*/
@NotBlank(message = "更新IP不能为空", groups = { AddGroup.class, EditGroup.class })
private String updateIp;
}

View File

@@ -0,0 +1,70 @@
package org.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.system.domain.ChatConfig;
import java.io.Serial;
import java.io.Serializable;
/**
* 配置信息视图对象 chat_config
*
* @author ageerle
* @date 2025-04-08
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatConfig.class)
public class ChatConfigVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 配置类型
*/
@ExcelProperty(value = "配置类型")
private String category;
/**
* 配置名称
*/
@ExcelProperty(value = "配置名称")
private String configName;
/**
* 配置值
*/
@ExcelProperty(value = "配置值")
private String configValue;
/**
* 说明
*/
@ExcelProperty(value = "说明")
private String configDict;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 更新IP
*/
@ExcelProperty(value = "更新IP")
private String updateIp;
}

View File

@@ -1,45 +0,0 @@
package org.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 【请填写功能名称】视图对象 sys_user_model
*
* @author Lion Li
* @date 2024-08-03
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = SysUserModel.class)
public class SysUserModelVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@ExcelProperty(value = "id")
private Long id;
/**
* 模型id
*/
@ExcelProperty(value = "模型id")
private Long mid;
/**
* 用户组id
*/
@ExcelProperty(value = "用户组id")
private Long gid;
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 应用市场Mapper接口
*
* @author Lion Li
* @date 2024-03-19
*/
public interface ChatAppStoreMapper extends BaseMapperPlus<ChatAppStore, ChatAppStoreVo> {
}

View File

@@ -1,15 +1,16 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.system.domain.ChatConfig;
import org.ruoyi.system.domain.vo.ChatConfigVo;
/**
* 对话配置信息
Mapper接口
* 配置信息Mapper接口
*
* @author Lion Li
* @date 2024-04-13
* @author ageerle
* @date 2025-04-08
*/
public interface ChatConfigMapper extends BaseMapperPlus<ChatConfig, ChatConfigVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* gpts管理Mapper接口
*
* @author Lion Li
* @date 2024-07-09
*/
public interface ChatGptsMapper extends BaseMapperPlus<ChatGpts, ChatGptsVo> {
}

View File

@@ -1,14 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 聊天消息Mapper接口
*
* @author Lion Li
* @date 2023-11-26
*/
public interface ChatMessageMapper extends BaseMapperPlus<ChatMessage, ChatMessageVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 插件管理Mapper接口
*
* @author ageerle
* @date 2025-03-30
*/
public interface ChatPluginMapper extends BaseMapperPlus<ChatPlugin, ChatPluginVo> {
}

View File

@@ -1,14 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 聊天消息Mapper接口
*
* @author Lion Li
* @date 2023-11-26
*/
public interface ChatTokenMapper extends BaseMapperPlus<ChatToken, ChatTokenVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 访客管理Mapper接口
*
* @author Lion Li
* @date 2024-07-14
*/
public interface ChatVisitorUsageMapper extends BaseMapperPlus<ChatVisitorUsage, ChatVisitorUsageVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 用户兑换记录Mapper接口
*
* @author Lion Li
* @date 2024-05-03
*/
public interface ChatVoucherMapper extends BaseMapperPlus<ChatVoucher, ChatVoucherVo> {
}

View File

@@ -1,14 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.system.domain.vo.cover.CoverVo;
/**
* 翻唱Mapper接口
*
* @author NSL
* @since 2024-12-25
*/
public interface CoverMapper extends BaseMapperPlus<Cover, CoverVo> {
}

View File

@@ -1,22 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.system.domain.vo.cover.CoverPromptAudioVo;
import java.util.List;
/**
* 翻唱用户参考音频Mapper接口
*
* @author NSL
* @since 2024-12-25
*/
public interface CoverPromptAudioMapper extends BaseMapperPlus<CoverPromptAudio, CoverPromptAudioVo> {
/**
* 获取最近一次翻唱记录
* @param userId 用户id
* @return 翻唱记录
*/
List<CoverPromptAudioVo> selectLatestVoByUserId(Long userId);
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 支付订单Mapper接口
*
* @author Lion Li
* @date 2024-04-16
*/
public interface PaymentOrdersMapper extends BaseMapperPlus<PaymentOrder, PaymentOrdersVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 系统模型Mapper接口
*
* @author Lion Li
* @date 2024-04-04
*/
public interface SysModelMapper extends BaseMapperPlus<SysModel, SysModelVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 套餐管理Mapper接口
*
* @author Lion Li
* @date 2024-05-05
*/
public interface SysPackagePlanMapper extends BaseMapperPlus<SysPackagePlan, SysPackagePlanVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 【请填写功能名称】Mapper接口
*
* @author Lion Li
* @date 2024-08-03
*/
public interface SysUserGroupMapper extends BaseMapperPlus<SysUserGroup, SysUserGroupVo> {
}

View File

@@ -1,14 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.system.domain.vo.SysUserModelVo;
/**
* 【请填写功能名称】Mapper接口
*
* @author Lion Li
* @date 2024-08-03
*/
public interface SysUserModelMapper extends BaseMapperPlus<SysUserModel, SysUserModelVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 【请填写功能名称】Mapper接口
*
* @author Lion Li
* @date 2024-05-01
*/
public interface WxRobConfigMapper extends BaseMapperPlus<WxRobConfig, WxRobConfigVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 【请填写功能名称】Mapper接口
*
* @author Lion Li
* @date 2024-05-01
*/
public interface WxRobKeywordMapper extends BaseMapperPlus<WxRobKeyword, WxRobKeywordVo> {
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 【请填写功能名称】Mapper接口
*
* @author Lion Li
* @date 2024-05-01
*/
public interface WxRobRelationMapper extends BaseMapperPlus<WxRobRelation, WxRobRelationVo> {
}

View File

@@ -1,28 +0,0 @@
package org.ruoyi.system.runner;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.system.service.ISysOssConfigService;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* 初始化 system 模块对应业务数据
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class SystemApplicationRunner implements ApplicationRunner {
private final ISysOssConfigService ossConfigService;
@Override
public void run(ApplicationArguments args) {
ossConfigService.init();
log.info("初始化OSS配置成功");
}
}

View File

@@ -1,433 +0,0 @@
package org.ruoyi.system.service;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.math.NumberUtils;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.constant.TenantConstants;
import org.ruoyi.common.core.domain.dto.RoleDTO;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
import org.ruoyi.common.core.domain.model.VisitorLoginUser;
import org.ruoyi.common.core.enums.*;
import org.ruoyi.common.core.exception.user.CaptchaException;
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
import org.ruoyi.common.core.exception.user.UserException;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.*;
import org.ruoyi.common.log.event.LogininforEvent;
import org.ruoyi.common.redis.utils.RedisUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.tenant.exception.TenantException;
import org.ruoyi.common.tenant.helper.TenantHelper;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.bo.SysUserBo;
import org.ruoyi.system.domain.vo.LoginVo;
import org.ruoyi.system.domain.vo.SysTenantVo;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
/**
* 登录校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {
private final SysUserMapper userMapper;
private final ISysPermissionService permissionService;
private final ISysTenantService tenantService;
private final WxMaService wxMaService;
private final ISysUserService userService;
private final ConfigService configService;
@Value("${user.password.maxRetryCount}")
private Integer maxRetryCount;
@Value("${user.password.lockTime}")
private Integer lockTime;
/**
* 获取微信
* @param xcxCode 获取xcxCode
*/
public String getOpenidFromCode(String xcxCode) {
try {
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(xcxCode);
return sessionInfo.getOpenid();
} catch (WxErrorException e) {
e.printStackTrace();
return null;
}
}
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String tenantId, String username, String password, String code, String uuid) {
SysUserVo user = loadUserByUsername(tenantId, username);
checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue();
}
public String smsLogin(String tenantId, String phonenumber, String smsCode) {
// 校验租户
checkTenant(tenantId);
// 通过手机号查找用户
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue();
}
public String emailLogin(String tenantId, String email, String emailCode) {
// 校验租户
checkTenant(tenantId);
// 通过手机号查找用户
SysUserVo user = loadUserByEmail(tenantId, email);
checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue();
}
/**
* 游客登录
*
* @param loginBody
* @return String
* @Date 2023/5/18
**/
public void visitorLogin(VisitorLoginBody loginBody) {
String openid = "";
// PC端游客登录
if (LoginUserType.PC.getCode().equals(loginBody.getType())) {
openid = loginBody.getCode();
} else {
// 小程序匿名登录
try {
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(loginBody.getCode());
openid = session.getOpenid();
} catch (WxErrorException e) {
log.error(e.getMessage(), e);
} finally {
// 清理ThreadLocal
WxMaConfigHolder.remove();
}
}
}
public LoginVo mpLogin(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
SysUserVo user = userService.selectUserByOpenId(openid);
VisitorLoginUser loginUser = new VisitorLoginUser();
if (ObjectUtil.isNull(user)) {
SysUserBo sysUser = new SysUserBo();
// 改为自增
String name = "用户" + UUID.randomUUID().toString().replace("-", "");
// 设置默认用户名
sysUser.setUserName(name);
// 设置默认昵称
sysUser.setNickName(name);
// 设置默认密码
sysUser.setPassword(BCrypt.hashpw("123456"));
// 设置微信openId
sysUser.setOpenId(openid);
String configValue = configService.getConfigValue("mail", "amount");
// 设置默认余额
sysUser.setUserBalance(NumberUtils.toDouble(configValue, 1));
// 注册用户,设置默认租户为0
SysUser registerUser = userService.registerUser(sysUser, "0");
// 构建登录用户信息
loginUser.setTenantId("0");
loginUser.setUserId(registerUser.getUserId());
loginUser.setUsername(registerUser.getUserName());
loginUser.setUserType(UserType.APP_USER.getUserType());
loginUser.setOpenid(openid);
loginUser.setNickName(registerUser.getNickName());
} else {
// 此处可根据登录用户的数据不同 自行创建 loginUser
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType());
loginUser.setNickName(user.getNickName());
loginUser.setAvatar(user.getWxAvatar());
loginUser.setOpenid(openid);
}
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
LoginVo loginVo = new LoginVo();
// 生成令牌
loginVo.setToken(StpUtil.getTokenValue());
loginVo.setUserInfo(loginUser);
return loginVo;
}
/**
* 退出登录
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
StpUtil.logout();
if (loginUser !=null) {
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
}
} catch (NotLoginException ignored) {
}
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
/**
* 校验邮箱验证码
*/
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(emailCode);
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
private SysUserVo loadUserByUsername(String tenantId, String username) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().select(SysUser::getUserName, SysUser::getStatus).eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId).eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
if (TenantHelper.isEnable()) {
return userMapper.selectTenantUserByUserName(username, tenantId);
}
return userMapper.selectUserByUserName(username);
}
private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().select(SysUser::getPhonenumber, SysUser::getStatus).eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId).eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
if (TenantHelper.isEnable()) {
return userMapper.selectTenantUserByPhonenumber(phonenumber, tenantId);
}
return userMapper.selectUserByPhonenumber(phonenumber);
}
private SysUserVo loadUserByEmail(String tenantId, String email) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().select(SysUser::getPhonenumber, SysUser::getStatus).eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId).eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
if (TenantHelper.isEnable()) {
return userMapper.selectTenantUserByEmail(email, tenantId);
}
return userMapper.selectUserByEmail(email);
}
/**
* 构建登录用户
*/
private LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setAvatar(user.getAvatar());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
loginUser.setRoles(roles);
return loginUser;
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(ServletUtils.getClientIP());
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(userId);
userMapper.updateById(sysUser);
}
/**
* 登录校验
*/
private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
// 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
if (supplier.get()) {
// 是否第一次
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
// 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber);
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
private void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime()) && new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
}

View File

@@ -1,153 +0,0 @@
package org.ruoyi.system.service;
import cn.dev33.satoken.secure.BCrypt;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.math.NumberUtils;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.domain.model.RegisterBody;
import org.ruoyi.common.core.exception.base.BaseException;
import org.ruoyi.common.core.exception.user.CaptchaException;
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
import org.ruoyi.common.core.exception.user.UserException;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.MessageUtils;
import org.ruoyi.common.core.utils.ServletUtils;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.log.event.LogininforEvent;
import org.ruoyi.common.redis.utils.RedisUtils;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.SysUserRole;
import org.ruoyi.system.domain.bo.SysUserBo;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.mapper.SysUserRoleMapper;
import org.springframework.stereotype.Service;
/**
* 注册校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysRegisterService {
private final ISysUserService userService;
private final SysUserRoleMapper userRoleMapper;
private final ConfigService configService;
/**
* 注册
*/
public void register(RegisterBody registerBody) {
String tenantId = Constants.TENANT_ID;
if(StringUtils.isNotBlank(registerBody.getTenantId())){
tenantId = registerBody.getTenantId();
}
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 检查验证码是否正确
validateEmail(username,registerBody.getCode());
SysUserBo sysUser = new SysUserBo();
sysUser.setDomainName(registerBody.getDomainName());
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(BCrypt.hashpw(password));
if (!userService.checkUserNameUnique(sysUser)) {
throw new UserException("添加用户失败", username);
}
String configValue = configService.getConfigValue("mail", "amount");
// 设置默认余额
sysUser.setUserBalance(NumberUtils.toDouble(configValue,1));
SysUser user = userService.registerUser(sysUser, tenantId);
if (user == null) {
throw new UserException("用户注册失败!");
}
// 设置默认角色
SysUserRole sysRole = new SysUserRole();
sysRole.setUserId(user.getUserId());
sysRole.setRoleId(1L);
userRoleMapper.insert(sysRole);
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 重置密码
*/
public void resetPassWord(RegisterBody registerBody) {
String username = registerBody.getUsername();
String password = registerBody.getPassword();
SysUserVo user = userService.selectUserByUserName(username);
if(user == null){
throw new UserException(String.format("用户【%s】,未注册!",username));
}
// 检查验证码是否正确
validateEmail(username,registerBody.getCode());
userService.resetUserPwd(user.getUserId(),BCrypt.hashpw(password));
}
/**
* 校验邮箱验证码
*
* @param username 用户名
*/
public void validateEmail(String username,String code) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + username;
String captcha = RedisUtils.getCacheObject(key);
if(code.equals(captcha)){
RedisUtils.deleteObject(captcha);
}else {
throw new BaseException("验证码错误,请重试!");
}
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
}

View File

@@ -0,0 +1,51 @@
package org.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.system.domain.ChatConfig;
import org.ruoyi.system.domain.bo.ChatConfigBo;
import org.ruoyi.system.domain.vo.ChatConfigVo;
import org.ruoyi.system.mapper.ChatConfigMapper;
import org.springframework.stereotype.Service;
/**
* 配置信息Service业务层处理
*
* @author ageerle
* @date 2025-04-08
*/
@RequiredArgsConstructor
@Service
public class ChatConfigServiceImpl implements ConfigService {
private final ChatConfigMapper baseMapper;
/**
* 根据配置类型和配置key获取值
*
* @param category 分类
* @param configKey key名称
* @return
*/
@Override
public String getConfigValue(String category,String configKey) {
ChatConfigBo bo = new ChatConfigBo();
bo.setCategory(category);
bo.setConfigName(configKey);
LambdaQueryWrapper<ChatConfig> lqw = buildQueryWrapper(bo);
ChatConfigVo chatConfigVo = baseMapper.selectVoOne(lqw);
return chatConfigVo.getConfigValue();
}
private LambdaQueryWrapper<ChatConfig> buildQueryWrapper(ChatConfigBo bo) {
LambdaQueryWrapper<ChatConfig> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getCategory()), ChatConfig::getCategory, bo.getCategory());
lqw.eq(StringUtils.isNotBlank(bo.getConfigName()), ChatConfig::getConfigName, bo.getConfigName());
lqw.eq(StringUtils.isNotBlank(bo.getConfigValue()), ChatConfig::getConfigValue, bo.getConfigValue());
return lqw;
}
}

View File

@@ -1,106 +0,0 @@
package org.ruoyi.system.service.impl;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.ruoyi.system.mapper.SysUserGroupMapper;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 【请填写功能名称】Service业务层处理
*
* @author Lion Li
* @date 2024-08-03
*/
@RequiredArgsConstructor
@Service
public class SysUserGroupServiceImpl implements ISysUserGroupService {
private final SysUserGroupMapper baseMapper;
/**
* 查询【请填写功能名称】
*/
@Override
public SysUserGroupVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 查询【请填写功能名称】列表
*/
@Override
public TableDataInfo<SysUserGroupVo> queryPageList(SysUserGroupBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysUserGroup> lqw = buildQueryWrapper(bo);
Page<SysUserGroupVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询【请填写功能名称】列表
*/
@Override
public List<SysUserGroupVo> queryList(SysUserGroupBo bo) {
LambdaQueryWrapper<SysUserGroup> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<SysUserGroup> buildQueryWrapper(SysUserGroupBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysUserGroup> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getGroupName()), SysUserGroup::getGroupName, bo.getGroupName());
lqw.eq(StringUtils.isNotBlank(bo.getUpdateIp()), SysUserGroup::getUpdateIp, bo.getUpdateIp());
return lqw;
}
/**
* 新增【请填写功能名称】
*/
@Override
public Boolean insertByBo(SysUserGroupBo bo) {
SysUserGroup add = MapstructUtils.convert(bo, SysUserGroup.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改【请填写功能名称】
*/
@Override
public Boolean updateByBo(SysUserGroupBo bo) {
SysUserGroup update = MapstructUtils.convert(bo, SysUserGroup.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SysUserGroup entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除【请填写功能名称】
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -1,62 +0,0 @@
package org.ruoyi.system.util;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* Java 使用 AES 加密算法进行加密解密
*/
@Component
public class AesUtils {
/**
* 秘钥(需要使用长度为16、24或32的字节数组作为AES算法的密钥否则就会遇到java.security.InvalidKeyException异常)
*/
// @Value("${ase.util.secret}")
public String key;
/**
* AES算法加密
*
* @Param:text原文
* @Param:key密钥
*/
@SneakyThrows
public String aesEncrypt(String text) {
// 创建AES加密算法实例(根据传入指定的秘钥进行加密)
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
// 初始化为加密模式,并将密钥注入到算法中
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 将传入的文本加密
byte[] encrypted = cipher.doFinal(text.getBytes());
//生成密文
// 将密文进行Base64编码方便传输
return Base64.getEncoder().encodeToString(encrypted);
}
/**
* AES算法解密
*
* @Param:base64Encrypted密文
* @Param:key密钥
*/
public String aesDecrypt(String base64Encrypted) throws Exception {
// 创建AES解密算法实例
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
// 初始化为解密模式,并将密钥注入到算法中
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 将Base64编码的密文解码
byte[] encrypted = Base64.getDecoder().decode(base64Encrypted);
// 解密
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted);
}
}

View File

@@ -1,67 +0,0 @@
package org.ruoyi.system.util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.ruoyi.common.core.service.ConfigService;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @author WangLe
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class AudioOkHttpUtil {
private final ConfigService configService;
private static final String AUTHORIZATION = "Authorization";
private final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.build();
public String executeRequest(Request request) {
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response.body() != null ? response.body().string() : null;
} catch (IOException e) {
// 这里应根据实际情况使用适当的日志记录方式
log.error("请求失败: {}",e.getMessage());
return null;
}
}
public Request createPostRequest(String url, String json) {
MediaType JSON = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create(json, JSON);
return new Request.Builder()
.url(getKey("apiHost") + url)
.post(body)
.header("Content-Type", "application/json")
.header(AUTHORIZATION, "Bearer "+getKey("apiKey"))
.build();
}
public Request createGetRequest(String url) {
return new Request.Builder()
.url(getKey("apiHost") + url)
.header(AUTHORIZATION, "Bearer "+getKey("apiKey"))
.build();
}
public String getKey(String key) {
return configService.getConfigValue("audio", key);
}
}

View File

@@ -1,19 +0,0 @@
package org.ruoyi.system.util;
public class DesensitizationUtil {
public static String maskData(String data) {
if (data == null || data.length() <= 4) {
return data;
}
int start = 2;
int end = data.length() - 2;
StringBuilder masked = new StringBuilder();
masked.append(data, 0, start);
for (int i = start; i < end; i++) {
masked.append('*');
}
masked.append(data.substring(end));
return masked.toString();
}
}

View File

@@ -1,24 +0,0 @@
package org.ruoyi.system.util;
import org.apache.commons.lang3.RandomStringUtils;
import java.util.UUID;
/**
* @author https://www.wdbyte.com
*/
public class KeyUtils {
public synchronized static String key6() {
return RandomStringUtils.randomAlphanumeric(6);
}
public synchronized static String key16() {
return RandomStringUtils.randomAlphanumeric(16);
}
public static String uuid32() {
return UUID.randomUUID().toString().replace("-", "");
}
}

View File

@@ -1,24 +0,0 @@
package org.ruoyi.system.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;
public class OrderNumberGenerator {
// 订单编号前缀
private static final String PREFIX = "NO";
// 时间格式化
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmm");
// 生成订单编号
public static String generate() {
// 获取当前日期时间字符串
String dateTimeStr = DATE_FORMAT.format(new Date());
// 生成随机数 (这里举例生成一个5位随机数)
int randomNum = ThreadLocalRandom.current().nextInt(10000, 99999);
// 拼接订单编号
return dateTimeStr + randomNum;
}
}

View File

@@ -1,438 +0,0 @@
package org.ruoyi.system.util;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 文多多PPT-API
*
* @author NSL
* @since 2024-12-30
*/
public class WddPptApi {
public static final String BASE_URL = "https://docmee.cn";
private static Long DEFAULT_TIME_OUT = 5 * 60 * 1000L;
public static String createApiToken(String apiKey, String uid, Integer limit) {
String url = BASE_URL + "/api/user/createApiToken";
JSONObject body = new JSONObject();
body.put("uid", uid);
body.put("limit", limit);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("Api-Key", apiKey);
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("创建apiToken失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("创建apiToken异常" + result.getString("message"));
}
return result.getJSONObject("data").getString("token");
}
public static String parseFileData(String apiToken, File file, String content, String fileUrl) {
String url = BASE_URL + "/api/ppt/parseFileData";
HttpUtils.HttpRequest httpRequest = new HttpUtils.HttpRequest();
httpRequest.setUrl(url);
httpRequest.setMethod("POST");
httpRequest.addHeaders("token", apiToken);
MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create();
multipartEntity.setCharset(StandardCharsets.UTF_8);
if (file != null) {
multipartEntity.addBinaryBody("file", file);
}
if (content != null) {
multipartEntity.addTextBody("content", content, ContentType.create("text/plain", StandardCharsets.UTF_8));
}
if (fileUrl != null) {
multipartEntity.addTextBody("fileUrl", fileUrl, ContentType.create("text/plain", StandardCharsets.UTF_8));
}
httpRequest.setBody(multipartEntity.build());
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("解析文件或内容失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("解析文件或内容异常," + result.getString("message"));
}
return result.getJSONObject("data").getString("dataUrl");
}
/**
* 生产大纲
*/
public static String generateOutline(String apiToken, String subject, String dataUrl, String prompt) {
String url = BASE_URL + "/api/ppt/generateOutline";
JSONObject body = new JSONObject();
body.put("subject", subject);
body.put("dataUrl", dataUrl);
body.put("prompt", prompt);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
StringBuilder sb = new StringBuilder();
HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> {
if (data == null || data.isEmpty()) {
return;
}
JSONObject json = JSONObject.parseObject(data);
if (Objects.equals(json.getInteger("status"), -1)) {
throw new RuntimeException(json.getString("error"));
}
String text = json.getString("text");
sb.append(text);
// 打印输出
System.out.print(text);
});
if (response.getStatus() != 200) {
throw new RuntimeException("生成大纲失败httpStatus=" + response.getStatus());
}
if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) {
JSONObject result = response.getResponseToJson();
throw new RuntimeException("生成大纲失败:" + result.getString("message"));
}
return sb.toString();
}
/**
* 生产大纲
*/
public static SseEmitter sseGenerateOutline(String apiToken, String subject, String dataUrl, String prompt) {
String url = BASE_URL + "/api/ppt/generateOutline";
JSONObject body = new JSONObject();
body.put("subject", subject);
body.put("dataUrl", dataUrl);
body.put("prompt", prompt);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
SseEmitter sseEmitter = new SseEmitter(DEFAULT_TIME_OUT);
StringBuilder sb = new StringBuilder();
new Thread(() -> {
HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> {
if (data == null || data.isEmpty()) {
return;
}
JSONObject json = JSONObject.parseObject(data);
Integer status = json.getInteger("status");
if (Objects.equals(status, -1)) {
throw new RuntimeException(json.getString("error"));
}
String text = json.getString("text");
try {
sseEmitter.send(SseEmitter.event().data(text));
} catch (IOException e) {
throw new RuntimeException(e);
}
sb.append(text);
// status 4 表示生成完成
if (status == 4) {
// 打印输出
System.out.print(sb);
sseEmitter.complete();
}
});
if (response.getStatus() != 200) {
throw new RuntimeException("生成大纲失败httpStatus=" + response.getStatus());
}
if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) {
JSONObject result = response.getResponseToJson();
throw new RuntimeException("生成大纲失败:" + result.getString("message"));
}
}).start();
return sseEmitter;
}
/**
* 生成大纲内容
*/
public static String generateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) {
String url = BASE_URL + "/api/ppt/generateContent";
JSONObject body = new JSONObject();
body.put("outlineMarkdown", outlineMarkdown);
body.put("dataUrl", dataUrl);
body.put("prompt", prompt);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
StringBuilder sb = new StringBuilder();
HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> {
if (data == null || data.isEmpty()) {
return;
}
JSONObject json = JSONObject.parseObject(data);
if (Objects.equals(json.getInteger("status"), -1)) {
throw new RuntimeException(json.getString("error"));
}
String text = json.getString("text");
sb.append(text);
// 打印输出
System.out.print(text);
});
if (response.getStatus() != 200) {
throw new RuntimeException("生成大纲内容失败httpStatus=" + response.getStatus());
}
if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) {
JSONObject result = response.getResponseToJson();
throw new RuntimeException("生成大纲内容失败:" + result.getString("message"));
}
return sb.toString();
}
/**
* 流式生成大纲内容
*/
public static SseEmitter sseGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) {
String url = BASE_URL + "/api/ppt/generateContent";
JSONObject body = new JSONObject();
body.put("outlineMarkdown", outlineMarkdown);
body.put("dataUrl", dataUrl);
body.put("prompt", prompt);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
SseEmitter sseEmitter = new SseEmitter(DEFAULT_TIME_OUT);
StringBuilder sb = new StringBuilder();
new Thread(() -> {
HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> {
if (data == null || data.isEmpty()) {
return;
}
JSONObject json = JSONObject.parseObject(data);
Integer status = json.getInteger("status");
if (Objects.equals(status, -1)) {
throw new RuntimeException(json.getString("error"));
}
String text = json.getString("text");
try {
sseEmitter.send(text);
} catch (IOException e) {
throw new RuntimeException(e);
}
sb.append(text);
// status 4 表示生成完成
if (status == 4) {
// 打印输出
System.out.print(sb);
sseEmitter.complete();
}
});
if (response.getStatus() != 200) {
throw new RuntimeException("生成大纲内容失败httpStatus=" + response.getStatus());
}
if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) {
JSONObject result = response.getResponseToJson();
throw new RuntimeException("生成大纲内容失败:" + result.getString("message"));
}
}).start();
return sseEmitter;
}
public static Map<String, String> asyncGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String templateId, String prompt) {
String url = BASE_URL + "/api/ppt/generateContent";
JSONObject body = new JSONObject();
body.put("asyncGenPptx", true);
body.put("templateId", templateId);
body.put("outlineMarkdown", outlineMarkdown);
body.put("dataUrl", dataUrl);
body.put("prompt", prompt);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
Map<String, String> pptInfo = new HashMap<>();
StringBuilder sb = new StringBuilder();
HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> {
if (data == null || data.isEmpty()) {
return;
}
JSONObject json = JSONObject.parseObject(data);
if (Objects.equals(json.getInteger("status"), -1)) {
throw new RuntimeException(json.getString("error"));
}
if (json.getString("pptId") != null) {
pptInfo.put("id", json.getString("pptId"));
}
String text = json.getString("text");
sb.append(text);
// 打印输出
System.out.print(text);
});
if (response.getStatus() != 200) {
throw new RuntimeException("生成大纲内容失败httpStatus=" + response.getStatus());
}
if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) {
JSONObject result = response.getResponseToJson();
throw new RuntimeException("生成大纲内容失败:" + result.getString("message"));
}
pptInfo.put("markdown", sb.toString());
return pptInfo;
}
public static String randomOneTemplateId(String apiToken) {
String url = BASE_URL + "/api/ppt/randomTemplates";
JSONObject body = new JSONObject();
body.put("size", 1);
JSONObject filters = new JSONObject();
filters.put("type", 1);
body.put("filters", filters);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("获取模板失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("获取模板异常," + result.getString("message"));
}
JSONArray data = result.getJSONArray("data");
JSONObject template = data.getJSONObject(0);
return template.getString("id");
}
/**
* 生成PPT
*/
public static JSONObject generatePptx(String apiToken, String templateId, String markdown, boolean pptxProperty) {
String url = BASE_URL + "/api/ppt/generatePptx";
JSONObject body = new JSONObject();
body.put("templateId", templateId);
body.put("outlineContentMarkdown", markdown);
body.put("pptxProperty", pptxProperty);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("生成PPT失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("生成PPT异常" + result.getString("message"));
}
return result.getJSONObject("data").getJSONObject("pptInfo");
}
public static JSONObject downloadPptx(String apiToken, String id) {
String url = BASE_URL + "/api/ppt/downloadPptx";
JSONObject body = new JSONObject();
body.put("id", id);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("下载PPT失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("下载PPT异常" + result.getString("message"));
}
return result.getJSONObject("data");
}
public static JSONObject directGeneratePptx(String apiToken, boolean stream, String templateId, String subject, String dataUrl, String prompt, boolean pptxProperty) {
String url = BASE_URL + "/api/ppt/directGeneratePptx";
JSONObject body = new JSONObject();
body.put("stream", stream);
body.put("templateId", templateId);
body.put("subject", subject);
body.put("dataUrl", dataUrl);
body.put("prompt", prompt);
body.put("pptxProperty", pptxProperty);
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body.toJSONString());
httpRequest.addHeaders("token", apiToken);
if (stream) {
// 流式生成
JSONObject[] pptInfo = new JSONObject[1];
HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> {
if (data == null || data.isEmpty()) {
return;
}
JSONObject json = JSONObject.parseObject(data);
if (Objects.equals(json.getInteger("status"), -1)) {
throw new RuntimeException(json.getString("error"));
}
if (Objects.equals(json.getInteger("status"), 4) && json.containsKey("result")) {
pptInfo[0] = json.getJSONObject("result");
}
String text = json.getString("text");
// 打印输出
System.out.print(text);
});
if (response.getStatus() != 200) {
throw new RuntimeException("生成PPT失败httpStatus=" + response.getStatus());
}
if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) {
JSONObject result = response.getResponseToJson();
throw new RuntimeException("生成PPT失败" + result.getString("message"));
}
return pptInfo[0];
} else {
// 非流式生成
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("生成PPT失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("生成PPT异常" + result.getString("message"));
}
return result.getJSONObject("data").getJSONObject("pptInfo");
}
}
/**
* 查询所有PPT列表
*/
public static JSONObject listAllPptx(String apiToken, String body) {
String url = BASE_URL + "/api/ppt/listAllPptx";
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body);
httpRequest.addHeaders("token", apiToken);
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("查询所有PPT列表失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("查询所有PPT列表异常" + result.getString("message"));
}
return result;
}
/**
* 分页查询 PPT 模板
*/
public static JSONObject getPptTemplates(String apiToken, String body) {
String url = BASE_URL + "/api/ppt/templates";
HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url);
httpRequest.setBody(body);
httpRequest.addHeaders("token", apiToken);
HttpUtils.HttpResponse response = HttpUtils.request(httpRequest);
if (response.getStatus() != 200) {
throw new RuntimeException("分页查询 PPT 模板失败httpStatus=" + response.getStatus());
}
JSONObject result = response.getResponseToJson();
if (result.getIntValue("code") != 0) {
throw new RuntimeException("分页查询 PPT 模板异常," + result.getString("message"));
}
return result;
}
}

View File

@@ -1,59 +0,0 @@
package org.ruoyi.system.util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.ruoyi.common.core.service.ConfigService;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* 绘声美音HTTP请求工具类
*
* @author NSL
* @since 2024-12-25
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class WeChatScanHttpUtil {
private final ConfigService configService;
private static final String TOKEN = "token";
private final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.build();
public String executeRequest(Request request) {
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response.body() != null ? response.body().string() : null;
} catch (IOException e) {
// 这里应根据实际情况使用适当的日志记录方式
log.error("请求失败: {}",e.getMessage());
return null;
}
}
public Request createPostRequest(String url, String json) {
RequestBody body = RequestBody.create(json, MediaType.get("application/json; charset=utf-8"));
return new Request.Builder()
.url(url)
.post(body)
.header("Content-Type", "application/json")
.header(TOKEN, getKey(TOKEN))
.build();
}
public String getKey(String key) {
return configService.getConfigValue("cover", key);
}
}

View File

@@ -1,81 +0,0 @@
package org.ruoyi.system.util;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.system.domain.model.WeixinQrCode;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.time.LocalDateTime;
/**
* @author https://www.wdbyte.com
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class WeixinApiUtil {
private final ConfigService configService;
private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
private static String ACCESS_TOKEN = null;
private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null;
/**
* 二维码 Ticket 过期时间
*/
private static int QR_CODE_TICKET_TIMEOUT = 10 * 60;
/**
* 获取 access token
*
* @return
*/
public synchronized String getAccessToken() {
if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) {
return ACCESS_TOKEN;
}
String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + getKey("appid") + "&secret="
+ getKey("secret");
String result = HttpUtil.get(api);
JSONObject jsonObject = JSON.parseObject(result);
ACCESS_TOKEN = jsonObject.getString("access_token");
ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10);
return ACCESS_TOKEN;
}
/**
* 获取二维码 Ticket
*
* https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
*
* @return
*/
public WeixinQrCode getQrCode() {
String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken();
String jsonBody = String.format("{\n"
+ " \"expire_seconds\": %d,\n"
+ " \"action_name\": \"QR_STR_SCENE\",\n"
+ " \"action_info\": {\n"
+ " \"scene\": {\n"
+ " \"scene_str\": \"%s\"\n"
+ " }\n"
+ " }\n"
+ "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32());
String result = HttpUtil.post(api, jsonBody);
log.info("get qr code params:{}", jsonBody);
log.info("get qr code result:{}", result);
WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class);
weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString());
return weixinQrCode;
}
public String getKey(String key) {
return configService.getConfigValue("weixin", key);
}
}

View File

@@ -1,66 +0,0 @@
package org.ruoyi.system.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.system.domain.model.ReceiveMessage;
/**
* @author https://www.wdbyte.com
*/
public class WeixinMsgUtil {
// 事件-关注
private static String EVENT_SUBSCRIBE = "subscribe";
/**
* 微信消息转对象
*
* @param xml
* @return
*/
public static ReceiveMessage msgToReceiveMessage(String xml) {
JSONObject jsonObject = JSON.parseObject(XmlUtil.xml2json(xml));
ReceiveMessage receiveMessage = new ReceiveMessage();
receiveMessage.setToUserName(jsonObject.getString("ToUserName"));
receiveMessage.setFromUserName(jsonObject.getString("FromUserName"));
receiveMessage.setCreateTime(jsonObject.getString("CreateTime"));
receiveMessage.setMsgType(jsonObject.getString("MsgType"));
receiveMessage.setContent(jsonObject.getString("Content"));
receiveMessage.setMsgId(jsonObject.getString("MsgId"));
receiveMessage.setEvent(jsonObject.getString("Event"));
receiveMessage.setTicket(jsonObject.getString("Ticket"));
return receiveMessage;
}
/**
* 是否是订阅事件
*
* @param receiveMessage
* @return
*/
public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) {
return StringUtils.equals(receiveMessage.getEvent(), EVENT_SUBSCRIBE);
}
/**
* 是否是二维码扫描事件
*
* @param receiveMessage
* @return
*/
public static boolean isScanQrCode(ReceiveMessage receiveMessage) {
return StringUtils.isNotEmpty(receiveMessage.getTicket());
}
/**
* 获取扫描的二维码 Ticket
*
* @param receiveMessage
* @return
*/
public static String getQrCodeTicket(ReceiveMessage receiveMessage) {
return receiveMessage.getTicket();
}
}

View File

@@ -1,34 +0,0 @@
package org.ruoyi.system.util;
import java.util.LinkedHashMap;
/**
* 微信二维码缓存工具类
*
* @author https://www.wdbyte.com
*/
public class WeixinQrCodeCacheUtil {
private static long MAX_CACHE_SIZE = 10000;
private static LinkedHashMap<String, String> QR_CODE_TICKET_MAP = new LinkedHashMap<>();
/**
* 增加一个 Ticket
* 首次 putvalue 为 ""
* 再次 put: value 有 openId若openId已经存在则已被扫码
*
* @param key
* @param value
*/
public synchronized static void put(String key, String value) {
QR_CODE_TICKET_MAP.put(key, value);
if (QR_CODE_TICKET_MAP.size() > MAX_CACHE_SIZE) {
String first = QR_CODE_TICKET_MAP.keySet().stream().findFirst().get();
QR_CODE_TICKET_MAP.remove(first);
}
}
public synchronized static String get(String key) {
return QR_CODE_TICKET_MAP.remove(key);
}
}

View File

@@ -1,28 +0,0 @@
package org.ruoyi.system.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* @author https://www.wdbyte.com
*/
@Slf4j
public class XmlUtil {
public static String xml2json(String requestBody) {
requestBody = StringUtils.trim(requestBody);
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = null;
try {
node = xmlMapper.readTree(requestBody.getBytes());
ObjectMapper jsonMapper = new ObjectMapper();
return jsonMapper.writeValueAsString(node);
} catch (Exception e) {
log.error("xml 2 json error,msg:" + e.getMessage(), e);
}
return null;
}
}