first commit

This commit is contained in:
zc
2026-02-27 10:16:46 +08:00
commit 0ee56404c2
705 changed files with 47675 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
package top.ysoft.admin.auth;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import top.ysoft.admin.auth.model.req.LoginReq;
import top.ysoft.admin.common.context.RoleContext;
import top.ysoft.admin.common.context.UserContext;
import top.ysoft.admin.common.context.UserContextHolder;
import top.ysoft.admin.common.context.UserExtraContext;
import top.ysoft.admin.common.enums.DisEnableStatusEnum;
import top.ysoft.admin.system.model.entity.DeptDO;
import top.ysoft.admin.system.model.entity.UserDO;
import top.ysoft.admin.system.model.resp.ClientResp;
import top.ysoft.admin.system.service.DeptService;
import top.ysoft.admin.system.service.OptionService;
import top.ysoft.admin.system.service.RoleService;
import top.ysoft.admin.system.service.UserService;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.core.validation.Validator;
import top.continew.starter.web.util.SpringWebUtils;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import static top.ysoft.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRATION_DAYS;
/**
* 登录处理器基类
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 14:52
*/
@Component
public abstract class AbstractLoginHandler<T extends LoginReq> implements LoginHandler<T> {
@Resource
protected OptionService optionService;
@Resource
protected UserService userService;
@Resource
protected RoleService roleService;
@Resource
private DeptService deptService;
@Resource
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
protected static final String CAPTCHA_EXPIRED = "验证码已失效";
protected static final String CAPTCHA_ERROR = "验证码错误";
protected static final String CLIENT_ID = "clientId";
@Override
public void preLogin(T req, ClientResp client, HttpServletRequest request) {
// 参数校验
Validator.validate(req);
}
@Override
public void postLogin(T req, ClientResp client, HttpServletRequest request) {
}
/**
* 认证
*
* @param user 用户信息
* @param client 终端信息
* @return token 令牌信息
*/
protected String authenticate(UserDO user, ClientResp client) {
// 获取权限、角色、密码过期天数
Long userId = user.getId();
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService
.listPermissionByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Set<RoleContext>> roleFuture = CompletableFuture.supplyAsync(() -> roleService
.listByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);
UserContext userContext = new UserContext(permissionFuture.join(), roleFuture
.join(), passwordExpirationDaysFuture.join());
BeanUtil.copyProperties(user, userContext);
// 设置登录配置参数
SaLoginModel model = new SaLoginModel();
model.setActiveTimeout(client.getActiveTimeout());
model.setTimeout(client.getTimeout());
model.setDevice(client.getClientType());
userContext.setClientType(client.getClientType());
model.setExtra(CLIENT_ID, client.getClientId());
userContext.setClientId(client.getClientId());
// 登录并缓存用户信息
StpUtil.login(userContext.getId(), model.setExtraData(BeanUtil.beanToMap(new UserExtraContext(SpringWebUtils
.getRequest()))));
UserContextHolder.setContext(userContext);
return StpUtil.getTokenValue();
}
/**
* 检查用户状态
*
* @param user 用户信息
*/
protected void checkUserStatus(UserDO user) {
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
DeptDO dept = deptService.getById(user.getDeptId());
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, dept.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员");
}
}

View File

@@ -0,0 +1,52 @@
package top.ysoft.admin.auth;
import jakarta.servlet.http.HttpServletRequest;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.LoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.system.model.resp.ClientResp;
/**
* 登录处理器
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 14:52
*/
public interface LoginHandler<T extends LoginReq> {
/**
* 登录
*
* @param req 登录请求参数
* @param client 终端信息
* @param request 请求对象
* @return 登录响应参数
*/
LoginResp login(T req, ClientResp client, HttpServletRequest request);
/**
* 登录前置处理
*
* @param req 登录请求参数
* @param client 终端信息
* @param request 请求对象
*/
void preLogin(T req, ClientResp client, HttpServletRequest request);
/**
* 登录后置处理
*
* @param req 登录请求参数
* @param client 终端信息
* @param request 请求对象
*/
void postLogin(T req, ClientResp client, HttpServletRequest request);
/**
* 获取认证类型
*
* @return 认证类型
*/
AuthTypeEnum getAuthType();
}

View File

@@ -0,0 +1,40 @@
package top.ysoft.admin.auth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.LoginReq;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
/**
* 登录处理器工厂
*
* @author KAI
* @author Charles7c
* @since 2024/12/20 15:16
*/
@Component
public class LoginHandlerFactory {
private final Map<AuthTypeEnum, LoginHandler<? extends LoginReq>> handlerMap = new EnumMap<>(AuthTypeEnum.class);
@Autowired
public LoginHandlerFactory(List<LoginHandler<? extends LoginReq>> handlers) {
for (LoginHandler<? extends LoginReq> handler : handlers) {
handlerMap.put(handler.getAuthType(), handler);
}
}
/**
* 根据认证类型获取
*
* @param authType 认证类型
* @return 认证处理器
*/
public LoginHandler<LoginReq> getHandler(AuthTypeEnum authType) {
return (LoginHandler<LoginReq>)handlerMap.get(authType);
}
}

View File

@@ -0,0 +1,42 @@
package top.ysoft.admin.auth.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.ysoft.admin.common.constant.UiConstants;
import top.continew.starter.core.enums.BaseEnum;
/**
* 认证类型枚举
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 14:52
*/
@Getter
@RequiredArgsConstructor
public enum AuthTypeEnum implements BaseEnum<String> {
/**
* 账号
*/
ACCOUNT("ACCOUNT", "账号", UiConstants.COLOR_SUCCESS),
/**
* 邮箱
*/
EMAIL("EMAIL", "邮箱", UiConstants.COLOR_PRIMARY),
/**
* 手机号
*/
PHONE("PHONE", "手机号", UiConstants.COLOR_PRIMARY),
/**
* 第三方账号
*/
SOCIAL("SOCIAL", "第三方账号", UiConstants.COLOR_ERROR);
private final String value;
private final String description;
private final String color;
}

View File

@@ -0,0 +1,110 @@
package top.ysoft.admin.auth.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import top.ysoft.admin.auth.AbstractLoginHandler;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.AccountLoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.common.constant.CacheConstants;
import top.ysoft.admin.common.constant.SysConstants;
import top.ysoft.admin.common.util.SecureUtils;
import top.ysoft.admin.system.enums.PasswordPolicyEnum;
import top.ysoft.admin.system.model.entity.UserDO;
import top.ysoft.admin.system.model.resp.ClientResp;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.core.validation.ValidationUtils;
import java.time.Duration;
/**
* 账号登录处理器
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 14:58
*/
@Component
@RequiredArgsConstructor
public class AccountLoginHandler extends AbstractLoginHandler<AccountLoginReq> {
private final PasswordEncoder passwordEncoder;
@Override
public LoginResp login(AccountLoginReq req, ClientResp client, HttpServletRequest request) {
// 解密密码
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfBlank(rawPassword, "密码解密失败");
// 验证用户名密码
String username = req.getUsername();
UserDO user = userService.getByUsername(username);
boolean isError = ObjectUtil.isNull(user) || !passwordEncoder.matches(rawPassword, user.getPassword());
// 检查账号锁定状态
this.checkUserLocked(req.getUsername(), request, isError);
ValidationUtils.throwIf(isError, "用户名或密码错误");
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
String token = this.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override
public void preLogin(AccountLoginReq req, ClientResp client, HttpServletRequest request) {
super.preLogin(req, client, request);
// 校验验证码
int loginCaptchaEnabled = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED");
if (SysConstants.YES.equals(loginCaptchaEnabled)) {
ValidationUtils.throwIfBlank(req.getCaptcha(), "验证码不能为空");
ValidationUtils.throwIfBlank(req.getUuid(), "验证码标识不能为空");
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + req.getUuid();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
RedisUtils.delete(captchaKey);
ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR);
}
}
@Override
public AuthTypeEnum getAuthType() {
return AuthTypeEnum.ACCOUNT;
}
/**
* 检测用户是否已被锁定
*
* @param username 用户名
* @param request 请求对象
* @param isError 是否登录错误
*/
private void checkUserLocked(String username, HttpServletRequest request, boolean isError) {
// 不锁定
int maxErrorCount = optionService.getValueByCode2Int(PasswordPolicyEnum.PASSWORD_ERROR_LOCK_COUNT.name());
if (maxErrorCount <= SysConstants.NO) {
return;
}
// 检测是否已被锁定
String key = CacheConstants.USER_PASSWORD_ERROR_KEY_PREFIX + RedisUtils.formatKey(username, JakartaServletUtil
.getClientIP(request));
int lockMinutes = optionService.getValueByCode2Int(PasswordPolicyEnum.PASSWORD_ERROR_LOCK_MINUTES.name());
Integer currentErrorCount = ObjectUtil.defaultIfNull(RedisUtils.get(key), 0);
CheckUtils.throwIf(currentErrorCount >= maxErrorCount, PasswordPolicyEnum.PASSWORD_ERROR_LOCK_MINUTES.getMsg()
.formatted(lockMinutes));
// 登录成功清除计数
if (!isError) {
RedisUtils.delete(key);
return;
}
// 登录失败递增计数
currentErrorCount++;
RedisUtils.set(key, currentErrorCount, Duration.ofMinutes(lockMinutes));
CheckUtils.throwIf(currentErrorCount >= maxErrorCount, PasswordPolicyEnum.PASSWORD_ERROR_LOCK_COUNT.getMsg()
.formatted(maxErrorCount, lockMinutes));
}
}

View File

@@ -0,0 +1,51 @@
package top.ysoft.admin.auth.handler;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import top.ysoft.admin.auth.AbstractLoginHandler;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.EmailLoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.common.constant.CacheConstants;
import top.ysoft.admin.system.model.entity.UserDO;
import top.ysoft.admin.system.model.resp.ClientResp;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.validation.ValidationUtils;
/**
* 邮箱登录处理器
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 14:58
*/
@Component
public class EmailLoginHandler extends AbstractLoginHandler<EmailLoginReq> {
@Override
public LoginResp login(EmailLoginReq req, ClientResp client, HttpServletRequest request) {
// 验证邮箱
UserDO user = userService.getByEmail(req.getEmail());
ValidationUtils.throwIfNull(user, "此邮箱未绑定本系统账号");
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override
public void preLogin(EmailLoginReq req, ClientResp client, HttpServletRequest request) {
String email = req.getEmail();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email;
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR);
RedisUtils.delete(captchaKey);
}
@Override
public AuthTypeEnum getAuthType() {
return AuthTypeEnum.EMAIL;
}
}

View File

@@ -0,0 +1,51 @@
package top.ysoft.admin.auth.handler;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import top.ysoft.admin.auth.AbstractLoginHandler;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.PhoneLoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.common.constant.CacheConstants;
import top.ysoft.admin.system.model.entity.UserDO;
import top.ysoft.admin.system.model.resp.ClientResp;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.validation.ValidationUtils;
/**
* 手机号登录处理器
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 14:59
*/
@Component
public class PhoneLoginHandler extends AbstractLoginHandler<PhoneLoginReq> {
@Override
public LoginResp login(PhoneLoginReq req, ClientResp client, HttpServletRequest request) {
// 验证手机号
UserDO user = userService.getByPhone(req.getPhone());
ValidationUtils.throwIfNull(user, "此手机号未绑定本系统账号");
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override
public void preLogin(PhoneLoginReq req, ClientResp client, HttpServletRequest request) {
String phone = req.getPhone();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone;
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR);
RedisUtils.delete(captchaKey);
}
@Override
public AuthTypeEnum getAuthType() {
return AuthTypeEnum.PHONE;
}
}

View File

@@ -0,0 +1,161 @@
package top.ysoft.admin.auth.handler;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.json.JSONUtil;
import com.xkcoding.justauth.AuthRequestFactory;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import org.springframework.stereotype.Component;
import top.ysoft.admin.auth.AbstractLoginHandler;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.SocialLoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.common.constant.RegexConstants;
import top.ysoft.admin.common.constant.SysConstants;
import top.ysoft.admin.common.enums.DisEnableStatusEnum;
import top.ysoft.admin.common.enums.GenderEnum;
import top.ysoft.admin.system.enums.MessageTemplateEnum;
import top.ysoft.admin.system.enums.MessageTypeEnum;
import top.ysoft.admin.system.model.entity.RoleDO;
import top.ysoft.admin.system.model.entity.UserDO;
import top.ysoft.admin.system.model.entity.UserSocialDO;
import top.ysoft.admin.system.model.req.MessageReq;
import top.ysoft.admin.system.model.resp.ClientResp;
import top.ysoft.admin.system.service.MessageService;
import top.ysoft.admin.system.service.UserRoleService;
import top.ysoft.admin.system.service.UserSocialService;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.messaging.websocket.util.WebSocketUtils;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
/**
* 第三方账号登录处理器
*
* @author KAI
* @author Charles7c
* @since 2024/12/25 14:21
*/
@Component
@RequiredArgsConstructor
public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
private final AuthRequestFactory authRequestFactory;
private final UserSocialService userSocialService;
private final UserRoleService userRoleService;
private final MessageService messageService;
private final ProjectProperties projectProperties;
@Override
public LoginResp login(SocialLoginReq req, ClientResp client, HttpServletRequest request) {
// 获取第三方登录信息
AuthRequest authRequest = this.getAuthRequest(req.getSource());
AuthCallback callback = new AuthCallback();
callback.setCode(req.getCode());
callback.setState(req.getState());
AuthResponse<AuthUser> response = authRequest.login(callback);
ValidationUtils.throwIf(!response.ok(), response.getMsg());
AuthUser authUser = response.getData();
// 如未绑定则自动注册新用户,保存或更新关联信息
String source = authUser.getSource();
String openId = authUser.getUuid();
UserSocialDO userSocial = userSocialService.getBySourceAndOpenId(source, openId);
UserDO user;
if (null == userSocial) {
String username = authUser.getUsername();
String nickname = authUser.getNickname();
UserDO existsUser = userService.getByUsername(username);
String randomStr = RandomUtil.randomString(RandomUtil.BASE_CHAR, 5);
if (null != existsUser || !ReUtil.isMatch(RegexConstants.USERNAME, username)) {
username = randomStr + IdUtil.fastSimpleUUID();
}
if (!ReUtil.isMatch(RegexConstants.GENERAL_NAME, nickname)) {
nickname = source.toLowerCase() + randomStr;
}
user = new UserDO();
user.setUsername(username);
user.setNickname(nickname);
user.setGender(GenderEnum.valueOf(authUser.getGender().name()));
user.setAvatar(authUser.getAvatar());
user.setDeptId(SysConstants.SUPER_DEPT_ID);
user.setStatus(DisEnableStatusEnum.ENABLE);
userService.save(user);
Long userId = user.getId();
RoleDO role = roleService.getByCode(SysConstants.SUPER_ROLE_CODE);
userRoleService.assignRolesToUser(Collections.singletonList(role.getId()), userId);
userSocial = new UserSocialDO();
userSocial.setUserId(userId);
userSocial.setSource(source);
userSocial.setOpenId(openId);
this.sendSecurityMsg(user);
} else {
user = BeanUtil.copyProperties(userService.getById(userSocial.getUserId()), UserDO.class);
}
// 检查用户状态
super.checkUserStatus(user);
userSocial.setMetaJson(JSONUtil.toJsonStr(authUser));
userSocial.setLastLoginTime(LocalDateTime.now());
userSocialService.saveOrUpdate(userSocial);
// 执行认证
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override
public void preLogin(SocialLoginReq req, ClientResp client, HttpServletRequest request) {
super.preLogin(req, client, request);
if (StpUtil.isLogin()) {
StpUtil.logout();
}
}
@Override
public AuthTypeEnum getAuthType() {
return AuthTypeEnum.SOCIAL;
}
/**
* 获取 AuthRequest
*
* @param source 平台名称
* @return AuthRequest
*/
private AuthRequest getAuthRequest(String source) {
try {
return authRequestFactory.get(source);
} catch (Exception e) {
throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source));
}
}
/**
* 发送安全消息
*
* @param user 用户信息
*/
private void sendSecurityMsg(UserDO user) {
MessageReq req = new MessageReq();
MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER;
req.setTitle(socialRegister.getTitle().formatted(projectProperties.getName()));
req.setContent(socialRegister.getContent().formatted(user.getNickname()));
req.setType(MessageTypeEnum.SECURITY);
messageService.add(req, CollUtil.toList(user.getId()));
List<String> tokenList = StpUtil.getTokenValueListByLoginId(user.getId());
for (String token : tokenList) {
WebSocketUtils.sendMessage(token, "1");
}
}
}

View File

@@ -0,0 +1,56 @@
package top.ysoft.admin.auth.model.query;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 在线用户查询条件
*
* @author Charles7c
* @since 2023/1/20 23:07
*/
@Data
@Schema(description = "在线用户查询条件")
public class OnlineUserQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户昵称
*/
@Schema(description = "用户昵称", example = "张三")
private String nickname;
/**
* 终端 ID
*/
@Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a")
private String clientId;
/**
* 登录时间
*/
@Schema(description = "登录时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private List<Date> loginTime;
/**
* 用户 ID
*/
@Schema(hidden = true)
private Long userId;
/**
* 角色 ID
*/
@Schema(hidden = true)
private Long roleId;
}

View File

@@ -0,0 +1,47 @@
package top.ysoft.admin.auth.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
/**
* 账号登录参数
*
* @author Charles7c
* @since 2022/12/21 20:43
*/
@Data
@Schema(description = "账号登录参数")
public class AccountLoginReq extends LoginReq {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
@Schema(description = "用户名", example = "zhangsan")
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码(加密)
*/
@Schema(description = "密码(加密)", example = "HHwZoiBwCfh0xLdWOAd0bHOkEZlIMMOQKJyeFUw9T3ArrhL57od2i42s1o0sSXKkeHPJXvQsninhPFH2lArDDQ==")
@NotBlank(message = "密码不能为空")
private String password;
/**
* 验证码
*/
@Schema(description = "验证码", example = "ABCD")
private String captcha;
/**
* 验证码标识
*/
@Schema(description = "验证码标识", example = "090b9a2c-1691-4fca-99db-e4ed0cff362f")
private String uuid;
}

View File

@@ -0,0 +1,40 @@
package top.ysoft.admin.auth.model.req;
import cn.hutool.core.lang.RegexPool;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
/**
* 邮箱登录参数
*
* @author Charles7c
* @since 2023/10/23 20:15
*/
@Data
@Schema(description = "邮箱登录参数")
public class EmailLoginReq extends LoginReq {
@Serial
private static final long serialVersionUID = 1L;
/**
* 邮箱
*/
@Schema(description = "邮箱", example = "123456789@qq.com")
@NotBlank(message = "邮箱不能为空")
@Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误")
private String email;
/**
* 验证码
*/
@Schema(description = "验证码", example = "888888")
@NotBlank(message = "验证码不能为空")
@Length(max = 6, message = "验证码非法")
private String captcha;
}

View File

@@ -0,0 +1,46 @@
package top.ysoft.admin.auth.model.req;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import java.io.Serial;
import java.io.Serializable;
/**
* 登录参数基类
*
* @author KAI
* @author Charles7c
* @since 2024/12/22 15:16
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "authType", visible = true)
@JsonSubTypes({@JsonSubTypes.Type(value = AccountLoginReq.class, name = "ACCOUNT"),
@JsonSubTypes.Type(value = EmailLoginReq.class, name = "EMAIL"),
@JsonSubTypes.Type(value = PhoneLoginReq.class, name = "PHONE"),
@JsonSubTypes.Type(value = SocialLoginReq.class, name = "SOCIAL")})
@Schema(description = "基础登录参数")
public class LoginReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 终端 ID
*/
@Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a")
@NotBlank(message = "终端ID不能为空")
private String clientId;
/**
* 认证类型
*/
@Schema(description = "认证类型", example = "ACCOUNT")
@NotNull(message = "认证类型非法")
private AuthTypeEnum authType;
}

View File

@@ -0,0 +1,40 @@
package top.ysoft.admin.auth.model.req;
import cn.hutool.core.lang.RegexPool;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
/**
* 手机号登录参数
*
* @author Charles7c
* @since 2023/10/26 22:37
*/
@Data
@Schema(description = "手机号登录参数")
public class PhoneLoginReq extends LoginReq {
@Serial
private static final long serialVersionUID = 1L;
/**
* 手机号
*/
@Schema(description = "手机号", example = "13811111111")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误")
private String phone;
/**
* 验证码
*/
@Schema(description = "验证码", example = "8888")
@NotBlank(message = "验证码不能为空")
@Length(max = 4, message = "验证码非法")
private String captcha;
}

View File

@@ -0,0 +1,43 @@
package top.ysoft.admin.auth.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
/**
* 第三方账号登录参数
*
* @author KAI
* @author Charles7c
* @since 2024/12/25 15:43
*/
@Data
@Schema(description = "第三方账号登录参数")
public class SocialLoginReq extends LoginReq {
@Serial
private static final long serialVersionUID = 1L;
/**
* 第三方登录平台
*/
@Schema(description = "第三方登录平台", example = "gitee")
@NotBlank(message = "第三方登录平台不能为空")
private String source;
/**
* 授权码
*/
@Schema(description = "授权码", example = "a08d33e9e577fb339de027499784ed4e871d6f62ae65b459153e906ab546bd56")
@NotBlank(message = "授权码不能为空")
private String code;
/**
* 状态码
*/
@Schema(description = "状态码", example = "2ca8d8baf437eb374efaa1191a3d")
@NotBlank(message = "状态码不能为空")
private String state;
}

View File

@@ -0,0 +1,59 @@
package top.ysoft.admin.auth.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 验证码信息
*
* @author Charles7c
* @since 2022/12/11 13:55
*/
@Data
@Builder
@Schema(description = "验证码信息")
public class CaptchaResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 验证码标识
*/
@Schema(description = "验证码标识", example = "090b9a2c-1691-4fca-99db-e4ed0cff362f")
private String uuid;
/**
* 验证码图片Base64编码带图片格式data:image/gif;base64
*/
@Schema(description = "验证码图片Base64编码带图片格式data:image/gif;base64", example = "data:image/png;base64,iVBORw0KGgoAAAAN...")
private String img;
/**
* 过期时间戳
*/
@Schema(description = "过期时间戳", example = "1714376969409")
private Long expireTime;
/**
* 是否启用
*/
@Schema(description = "是否启用", example = "true")
private Boolean isEnabled;
/**
* 构建验证码信息
*
* @param uuid 验证码标识
* @param img 验证码图片Base64编码带图片格式data:image/gif;base64
* @param expireTime 过期时间戳
* @return 验证码信息
*/
public static CaptchaResp of(String uuid, String img, Long expireTime) {
return CaptchaResp.builder().uuid(uuid).img(img).expireTime(expireTime).isEnabled(true).build();
}
}

View File

@@ -0,0 +1,29 @@
package top.ysoft.admin.auth.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 登录响应参数
*
* @author Charles7c
* @since 2022/12/21 20:42
*/
@Data
@Builder
@Schema(description = "登录响应参数")
public class LoginResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 令牌
*/
@Schema(description = "令牌", example = "eyJ0eXAiOiJlV1QiLCJhbGciqiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb29pbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiSjd4SUljYnU5cmNwU09vQ3Uyc1ND1BYYTYycFRjcjAifQ.KUPOYm-2wfuLUSfEEAbpGE527fzmkAJG7sMNcQ0pUZ8")
private String token;
}

View File

@@ -0,0 +1,102 @@
package top.ysoft.admin.auth.model.resp;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.AssembleMethod;
import cn.crane4j.annotation.ContainerMethod;
import cn.crane4j.annotation.MappingType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.ysoft.admin.auth.service.OnlineUserService;
import top.ysoft.admin.common.constant.ContainerConstants;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 在线用户信息
*
* @author Charles7c
* @since 2023/1/20 21:54
*/
@Data
@Schema(description = "在线用户信息")
public class OnlineUserResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Schema(description = "ID", example = "1")
@Assemble(prop = ":nickname", container = ContainerConstants.USER_NICKNAME)
private Long id;
/**
* 令牌
*/
@Schema(description = "令牌", example = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiTUd6djdyOVFoeHEwdVFqdFAzV3M5YjVJRzh4YjZPSEUifQ.7q7U3ouoN7WPhH2kUEM7vPe5KF3G_qavSG-vRgIxKvE")
@AssembleMethod(prop = ":lastActiveTime", targetType = OnlineUserService.class, method = @ContainerMethod(bindMethod = "getLastActiveTime", type = MappingType.ORDER_OF_KEYS))
private String token;
/**
* 用户名
*/
@Schema(description = "用户名", example = "zhangsan")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称", example = "张三")
private String nickname;
/**
* 终端类型
*/
@Schema(description = "终端类型", example = "PC")
private String clientType;
/**
* 终端 ID
*/
@Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a")
private String clientId;
/**
* 登录 IP
*/
@Schema(description = "登录 IP", example = "")
private String ip;
/**
* 登录地点
*/
@Schema(description = "登录地点", example = "中国北京北京市")
private String address;
/**
* 浏览器
*/
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
private String browser;
/**
* 操作系统
*/
@Schema(description = "操作系统", example = "Windows 10")
private String os;
/**
* 登录时间
*/
@Schema(description = "登录时间", example = "2023-08-08 08:08:08", type = "string")
private LocalDateTime loginTime;
/**
* 最后活跃时间
*/
@Schema(description = "最后活跃时间", example = "2023-08-08 08:08:08", type = "string")
private LocalDateTime lastActiveTime;
}

View File

@@ -0,0 +1,114 @@
package top.ysoft.admin.auth.model.resp;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 路由信息
*
* @author Charles7c
* @since 2023/2/26 22:51
*/
@Data
@Schema(description = "路由信息")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class RouteResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Schema(description = "ID", example = "1010")
private Long id;
/**
* 上级菜单 ID
*/
@Schema(description = "上级菜单ID", example = "1000")
private Long parentId;
/**
* 标题
*/
@Schema(description = "标题", example = "用户管理")
private String title;
/**
* 类型
*/
@Schema(description = "类型", example = "2")
private Integer type;
/**
* 路由地址
*/
@Schema(description = "路由地址", example = "/system/user")
private String path;
/**
* 组件名称
*/
@Schema(description = "组件名称", example = "User")
private String name;
/**
* 组件路径
*/
@Schema(description = "组件路径", example = "/system/user/index")
private String component;
/**
* 重定向地址
*/
@Schema(description = "重定向地址")
private String redirect;
/**
* 图标
*/
@Schema(description = "图标", example = "user")
private String icon;
/**
* 是否外链
*/
@Schema(description = "是否外链", example = "false")
private Boolean isExternal;
/**
* 是否缓存
*/
@Schema(description = "是否缓存", example = "false")
private Boolean isCache;
/**
* 是否隐藏
*/
@Schema(description = "是否隐藏", example = "false")
private Boolean isHidden;
/**
* 权限标识
*/
@Schema(description = "权限标识", example = "system:user:list")
private String permission;
/**
* 排序
*/
@Schema(description = "排序", example = "1")
private Integer sort;
/**
* 子路由列表
*/
@Schema(description = "子路由列表")
private List<RouteResp> children;
}

View File

@@ -0,0 +1,29 @@
package top.ysoft.admin.auth.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 三方账号授权认证响应信息
*
* @author Charles7c
* @since 2024/3/6 22:26
*/
@Data
@Builder
@Schema(description = "三方账号授权认证响应信息")
public class SocialAuthAuthorizeResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 授权 URL
*/
@Schema(description = "授权 URL", example = "https://gitee.com/oauth/authorize?response_type=code&client_id=5d271b7f638941812aaf8bfc2e2f08f06d6235ef934e0e39537e2364eb8452c4&redirect_uri=http://localhost:6609/social/callback?source=gitee&state=d4ea7129e2531050210e9c918cc007d7&scope=user_info")
private String authorizeUrl;
}

View File

@@ -0,0 +1,130 @@
package top.ysoft.admin.auth.model.resp;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.ysoft.admin.common.enums.GenderEnum;
import top.continew.starter.security.mask.annotation.JsonMask;
import top.continew.starter.security.mask.enums.MaskType;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Set;
/**
* 用户信息
*
* @author Charles7c
* @since 2022/12/29 20:15
*/
@Data
@Schema(description = "用户信息")
public class UserInfoResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Schema(description = "ID", example = "1")
private Long id;
/**
* 用户名
*/
@Schema(description = "用户名", example = "zhangsan")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称", example = "张三")
private String nickname;
/**
* 性别
*/
@Schema(description = "性别", example = "1")
private GenderEnum gender;
/**
* 邮箱
*/
@Schema(description = "邮箱", example = "c*******@126.com")
@JsonMask(MaskType.EMAIL)
private String email;
/**
* 手机号码
*/
@Schema(description = "手机号码", example = "188****8888")
@JsonMask(MaskType.MOBILE_PHONE)
private String phone;
/**
* 头像地址
*/
@Schema(description = "头像地址", example = "https://himg.bdimg.com/sys/portrait/item/public.1.81ac9a9e.rf1ix17UfughLQjNo7XQ_w.jpg")
private String avatar;
/**
* 描述
*/
@Schema(description = "描述", example = "张三描述信息")
private String description;
/**
* 最后一次修改密码时间
*/
@Schema(description = "最后一次修改密码时间", example = "2023-08-08 08:08:08", type = "string")
private LocalDateTime pwdResetTime;
/**
* 密码是否已过期
*/
@Schema(description = "密码是否已过期", example = "true")
private Boolean pwdExpired;
/**
* 创建时间
*/
@JsonIgnore
private LocalDateTime createTime;
/**
* 注册日期
*/
@Schema(description = "注册日期", example = "2023-08-08")
private LocalDate registrationDate;
/**
* 部门 ID
*/
@Schema(description = "部门 ID", example = "1")
private Long deptId;
/**
* 所属部门
*/
@Schema(description = "所属部门", example = "测试部")
private String deptName;
/**
* 权限码集合
*/
@Schema(description = "权限码集合", example = "[\"system:user:list\",\"system:user:add\"]")
private Set<String> permissions;
/**
* 角色编码集合
*/
@Schema(description = "角色编码集合", example = "[\"test\"]")
private Set<String> roles;
public LocalDate getRegistrationDate() {
return createTime.toLocalDate();
}
}

View File

@@ -0,0 +1,34 @@
package top.ysoft.admin.auth.service;
import jakarta.servlet.http.HttpServletRequest;
import top.ysoft.admin.auth.model.req.LoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.auth.model.resp.RouteResp;
import java.util.List;
/**
* 认证业务接口
*
* @author Charles7c
* @since 2022/12/21 21:48
*/
public interface AuthService {
/**
* 登录
*
* @param req 登录请求参数
* @param request 请求对象
* @return 登录响应参数
*/
LoginResp login(LoginReq req, HttpServletRequest request);
/**
* 构建路由树
*
* @param userId 用户 ID
* @return 路由树
*/
List<RouteResp> buildRouteTree(Long userId);
}

View File

@@ -0,0 +1,50 @@
package top.ysoft.admin.auth.service;
import top.ysoft.admin.auth.model.query.OnlineUserQuery;
import top.ysoft.admin.auth.model.resp.OnlineUserResp;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.time.LocalDateTime;
import java.util.List;
/**
* 在线用户业务接口
*
* @author Charles7c
* @since 2023/3/25 22:48
*/
public interface OnlineUserService {
/**
* 分页查询列表
*
* @param query 查询条件
* @param pageQuery 分页查询条件
* @return 分页列表信息
*/
PageResp<OnlineUserResp> page(OnlineUserQuery query, PageQuery pageQuery);
/**
* 查询列表
*
* @param query 查询条件
* @return 列表信息
*/
List<OnlineUserResp> list(OnlineUserQuery query);
/**
* 查询 Token 最后活跃时间
*
* @param token Token
* @return 最后活跃时间
*/
LocalDateTime getLastActiveTime(String token);
/**
* 踢出用户
*
* @param userId 用户 ID
*/
void kickOut(Long userId);
}

View File

@@ -0,0 +1,110 @@
package top.ysoft.admin.auth.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.ysoft.admin.auth.LoginHandler;
import top.ysoft.admin.auth.LoginHandlerFactory;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.LoginReq;
import top.ysoft.admin.auth.model.resp.LoginResp;
import top.ysoft.admin.auth.model.resp.RouteResp;
import top.ysoft.admin.auth.service.AuthService;
import top.ysoft.admin.common.constant.SysConstants;
import top.ysoft.admin.common.context.RoleContext;
import top.ysoft.admin.common.enums.DisEnableStatusEnum;
import top.ysoft.admin.system.enums.MenuTypeEnum;
import top.ysoft.admin.system.model.resp.ClientResp;
import top.ysoft.admin.system.model.resp.MenuResp;
import top.ysoft.admin.system.service.ClientService;
import top.ysoft.admin.system.service.MenuService;
import top.ysoft.admin.system.service.RoleService;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 认证业务实现
*
* @author Charles7c
* @since 2022/12/21 21:49
*/
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final LoginHandlerFactory loginHandlerFactory;
private final ClientService clientService;
private final RoleService roleService;
private final MenuService menuService;
private final CrudProperties crudProperties;
@Override
public LoginResp login(LoginReq req, HttpServletRequest request) {
AuthTypeEnum authType = req.getAuthType();
// 校验终端
ClientResp client = clientService.getByClientId(req.getClientId());
ValidationUtils.throwIfNull(client, "终端不存在");
ValidationUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(client.getStatus()), "终端已禁用");
ValidationUtils.throwIf(!client.getAuthType().contains(authType.getValue()), "该终端暂未授权 [{}] 认证", authType
.getDescription());
// 获取处理器
LoginHandler<LoginReq> loginHandler = loginHandlerFactory.getHandler(authType);
// 登录前置处理
loginHandler.preLogin(req, client, request);
// 登录
LoginResp loginResp = loginHandler.login(req, client, request);
// 登录后置处理
loginHandler.postLogin(req, client, request);
return loginResp;
}
@Override
public List<RouteResp> buildRouteTree(Long userId) {
Set<RoleContext> roleSet = roleService.listByUserId(userId);
if (CollUtil.isEmpty(roleSet)) {
return new ArrayList<>(0);
}
// 查询菜单列表
Set<MenuResp> menuSet = new LinkedHashSet<>();
if (roleSet.stream().anyMatch(r -> SysConstants.SUPER_ROLE_ID.equals(r.getId()))) {
menuSet.addAll(menuService.listByRoleId(SysConstants.SUPER_ROLE_ID));
} else {
roleSet.forEach(r -> menuSet.addAll(menuService.listByRoleId(r.getId())));
}
List<MenuResp> menuList = menuSet.stream().filter(m -> !MenuTypeEnum.BUTTON.equals(m.getType())).toList();
if (CollUtil.isEmpty(menuList)) {
return new ArrayList<>(0);
}
// 构建路由树
TreeField treeField = MenuResp.class.getDeclaredAnnotation(TreeField.class);
TreeNodeConfig treeNodeConfig = crudProperties.getTree().genTreeNodeConfig(treeField);
List<Tree<Long>> treeList = TreeUtil.build(menuList, treeField.rootId(), treeNodeConfig, (m, tree) -> {
tree.setId(m.getId());
tree.setParentId(m.getParentId());
tree.setName(m.getTitle());
tree.setWeight(m.getSort());
tree.putExtra("type", m.getType().getValue());
tree.putExtra("path", m.getPath());
tree.putExtra("name", m.getName());
tree.putExtra("component", m.getComponent());
tree.putExtra("redirect", m.getRedirect());
tree.putExtra("icon", m.getIcon());
tree.putExtra("isExternal", m.getIsExternal());
tree.putExtra("isCache", m.getIsCache());
tree.putExtra("isHidden", m.getIsHidden());
tree.putExtra("permission", m.getPermission());
});
return BeanUtil.copyToList(treeList, RouteResp.class);
}
}

View File

@@ -0,0 +1,137 @@
package top.ysoft.admin.auth.service.impl;
import cn.crane4j.annotation.AutoOperate;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.ysoft.admin.auth.model.query.OnlineUserQuery;
import top.ysoft.admin.auth.model.resp.OnlineUserResp;
import top.ysoft.admin.auth.service.OnlineUserService;
import top.ysoft.admin.common.context.UserContext;
import top.ysoft.admin.common.context.UserContextHolder;
import top.ysoft.admin.common.context.UserExtraContext;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 在线用户业务实现
*
* @author Charles7c
* @since 2023/3/25 22:49
*/
@Service
@RequiredArgsConstructor
public class OnlineUserServiceImpl implements OnlineUserService {
@Override
@AutoOperate(type = OnlineUserResp.class, on = "list")
public PageResp<OnlineUserResp> page(OnlineUserQuery query, PageQuery pageQuery) {
List<OnlineUserResp> list = this.list(query);
return PageResp.build(pageQuery.getPage(), pageQuery.getSize(), list);
}
@Override
public List<OnlineUserResp> list(OnlineUserQuery query) {
List<OnlineUserResp> list = new ArrayList<>();
// 查询所有在线 Token
List<String> tokenKeyList = StpUtil.searchTokenValue(StringConstants.EMPTY, 0, -1, false);
Map<Long, List<String>> tokenMap = tokenKeyList.stream().filter(tokenKey -> {
String token = StrUtil.subAfter(tokenKey, StringConstants.COLON, true);
// 忽略已过期或失效 Token
return StpUtil.getStpLogic().getTokenActiveTimeoutByToken(token) >= SaTokenDao.NEVER_EXPIRE;
})
.map(tokenKey -> StrUtil.subAfter(tokenKey, StringConstants.COLON, true))
.collect(Collectors.groupingBy(token -> Convert.toLong(StpUtil.getLoginIdByToken(token))));
// 筛选数据
for (Map.Entry<Long, List<String>> entry : tokenMap.entrySet()) {
Long userId = entry.getKey();
UserContext userContext = UserContextHolder.getContext(userId);
if (null == userContext || !this.isMatchNickname(query.getNickname(), userContext) || !this
.isMatchClientId(query.getClientId(), userContext)) {
continue;
}
List<Date> loginTimeList = query.getLoginTime();
entry.getValue().parallelStream().forEach(token -> {
UserExtraContext extraContext = UserContextHolder.getExtraContext(token);
if (!this.isMatchLoginTime(loginTimeList, extraContext.getLoginTime())) {
return;
}
OnlineUserResp resp = BeanUtil.copyProperties(userContext, OnlineUserResp.class);
BeanUtil.copyProperties(extraContext, resp);
resp.setToken(token);
list.add(resp);
});
}
// 设置排序
CollUtil.sort(list, Comparator.comparing(OnlineUserResp::getLoginTime).reversed());
return list;
}
@Override
public LocalDateTime getLastActiveTime(String token) {
long lastActiveTime = StpUtil.getStpLogic().getTokenLastActiveTime(token);
return lastActiveTime == SaTokenDao.NOT_VALUE_EXPIRE ? null : DateUtil.date(lastActiveTime).toLocalDateTime();
}
@Override
public void kickOut(Long userId) {
if (!StpUtil.isLogin(userId)) {
return;
}
StpUtil.logout(userId);
}
/**
* 是否匹配昵称
*
* @param nickname 昵称
* @param userContext 用户上下文信息
* @return 是否匹配昵称
*/
private boolean isMatchNickname(String nickname, UserContext userContext) {
if (StrUtil.isBlank(nickname)) {
return true;
}
return StrUtil.contains(userContext.getUsername(), nickname) || StrUtil.contains(UserContextHolder
.getNickname(userContext.getId()), nickname);
}
/**
* 是否匹配终端 ID
*
* @param clientId 终端 ID
* @param userContext 用户上下文信息
* @return 是否匹配终端 ID
*/
private boolean isMatchClientId(String clientId, UserContext userContext) {
if (StrUtil.isBlank(clientId)) {
return true;
}
return Objects.equals(userContext.getClientId(), clientId);
}
/**
* 是否匹配登录时间
*
* @param loginTimeList 登录时间列表
* @param loginTime 登录时间
* @return 是否匹配登录时间
*/
private boolean isMatchLoginTime(List<Date> loginTimeList, LocalDateTime loginTime) {
if (CollUtil.isEmpty(loginTimeList)) {
return true;
}
return DateUtil.isIn(DateUtil.date(loginTime).toJdkDate(), loginTimeList.get(0), loginTimeList.get(1));
}
}

View File

@@ -0,0 +1,23 @@
package top.ysoft.admin.consume.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.consume.model.entity.DownRecordDO;
import top.ysoft.admin.consume.model.resp.DownRecordResp;
/**
* 消费下发记录 Mapper
*
* @author zc
* @since 2026/02/25 11:01
*/
@Repository
public interface DownRecordMapper extends BaseMapper<DownRecordDO> {
IPage<DownRecordResp> selectRecordsPage(@Param("page") IPage<Object> page,
@Param(Constants.WRAPPER) QueryWrapper<DownRecordDO> queryWrapper);
}

View File

@@ -0,0 +1,14 @@
package top.ysoft.admin.consume.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.consume.model.entity.GroupDO;
import org.springframework.stereotype.Repository;
/**
* 消费组 Mapper
*
* @author zc
* @since 2026/01/27 13:48
*/
@Repository
public interface GroupMapper extends BaseMapper<GroupDO> {}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.consume.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
import top.ysoft.admin.consume.model.entity.GroupUserDO;
/**
* 消费组用户映射器
*
* @author zc
* @since 2026/01/27 13:48
*/
@Repository
public interface GroupUserMapper extends BaseMapper<GroupUserDO> {
}

View File

@@ -0,0 +1,40 @@
package top.ysoft.admin.consume.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.consume.model.entity.RechargeRecordDO;
import org.springframework.stereotype.Repository;
import top.ysoft.admin.consume.model.resp.RechargeRecordResp;
import java.util.List;
/**
* 充值记录 Mapper
*
* @author zc
* @since 2026/01/29 16:11
*/
@Repository
public interface RechargeRecordMapper extends BaseMapper<RechargeRecordDO> {
/**
* 查询充值记录分页
*
* @param page 分页查询条件
* @param queryWrapper 查询条件
* @return 分页信息
*/
IPage<RechargeRecordResp> selectRechargePage(@Param("page") IPage<Object> page,
@Param(Constants.WRAPPER) QueryWrapper<RechargeRecordDO> queryWrapper);
/**
* 导出充值记录
*
* @param queryWrapper 查询条件
* @return 充值记录列表
*/
List<RechargeRecordResp> selectExport(@Param(Constants.WRAPPER) QueryWrapper<RechargeRecordDO> queryWrapper);
}

View File

@@ -0,0 +1,40 @@
package top.ysoft.admin.consume.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.consume.model.entity.RecordDO;
import top.ysoft.admin.consume.model.resp.RecordResp;
import java.util.List;
/**
* 消费记录 Mapper
*
* @author zc
* @since 2026/01/21 17:53
*/
@Repository
public interface RecordMapper extends BaseMapper<RecordDO> {
/**
* 分页查询列表
*
* @param page 分页查询条件
* @param queryWrapper 查询条件
* @return 分页信息
*/
IPage<RecordResp> selectRecordsPage(@Param("page") IPage<Object> page,
@Param(Constants.WRAPPER) QueryWrapper<RecordDO> queryWrapper);
/**
* 导出列表
*
* @param queryWrapper 查询条件
* @return 列表信息
*/
List<RecordResp> selectRecordsExport(@Param(Constants.WRAPPER) QueryWrapper<RecordDO> queryWrapper);
}

View File

@@ -0,0 +1,69 @@
package top.ysoft.admin.consume.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.ysoft.admin.consume.model.query.ReportQuery;
import top.ysoft.admin.consume.model.resp.report.*;
import java.util.List;
@Repository
public interface ReportMapper {
/**
* 查询商户报表
*
* @param query 查询参数
* @return 商户报表列表
*/
List<MerchantReport> queryMerchantReport(ReportQuery query);
/**
* 查询报表总价
*
* @param query 查询参数
* @return 报表列表总价
*/
BaseReport totalReport(ReportQuery query);
/**
* 查询餐段报表
*
* @param query 查询参数
* @return 餐段报表列表
*/
List<MealReport> queryMealReport(ReportQuery query);
/**
* 查询店铺报表
*
* @param query 查询参数
* @return 分店报表列表
*/
List<BranchReport> queryBranchReport(ReportQuery query);
/**
* 查询人员报表
*
* @param query 查询参数
* @return 人员报表列表
*/
List<PeopleReport> queryPeopleReport(@Param("query") ReportQuery query,
@Param("size") Integer size,
@Param("offset") Integer offset);
/**
* 查询人员报表总数
*
* @param query 查询参数
* @return 人员报表总数
*/
long selectCount(ReportQuery query);
/**
* 查询人员报表导出数据
*
* @param query 查询参数
* @return 人员报表导出数据列表
*/
List<PeopleReport> queryPeopleExport(ReportQuery query);
}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.consume.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.consume.model.entity.TenantDO;
import org.springframework.stereotype.Repository;
/**
* 商户信息 Mapper
*
* @author zc
* @since 2026/01/30 14:55
*/
@Repository
public interface TenantMapper extends BaseMapper<TenantDO> {
}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.consume.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.consume.model.entity.TimeIntervalDO;
import org.springframework.stereotype.Repository;
/**
* 消费时段 Mapper
*
* @author zc
* @since 2026/01/30 14:56
*/
@Repository
public interface TimeIntervalMapper extends BaseMapper<TimeIntervalDO> {
}

View File

@@ -0,0 +1,58 @@
package top.ysoft.admin.consume.mapstruct;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import top.ysoft.admin.common.enums.ConsumeRechargeModeEnum;
import top.ysoft.admin.common.enums.ConsumeRechargeTypeEnum;
import top.ysoft.admin.common.enums.ConsumeRechargeWayEnum;
import top.ysoft.admin.consume.model.entity.RechargeRecordDO;
import top.ysoft.admin.consume.model.resp.RechargeRecordResp;
import java.util.List;
@Mapper(componentModel = "spring")
public interface RechargeRecordConvert {
@Mapping(target = "rechargeWay", expression = "java(convertRechargeWay(recordDO.getRechargeWay()))")
@Mapping(target = "rechargeMode", expression = "java(convertRechargeMode(recordDO.getRechargeMode()))")
@Mapping(target = "rechargeType", expression = "java(convertRechargeType(recordDO.getRechargeType()))")
RechargeRecordResp toRecordResp(RechargeRecordDO recordDO);
List<RechargeRecordResp> ListToRecordResp(List<RechargeRecordDO> recordDOS);
default ConsumeRechargeWayEnum convertRechargeWay(Integer rechargeWay) {
if (rechargeWay == null) {
return null;
}
for (ConsumeRechargeWayEnum way : ConsumeRechargeWayEnum.values()) {
if (way.getValue().equals(rechargeWay)) {
return way;
}
}
return null;
}
default ConsumeRechargeModeEnum convertRechargeMode(Integer rechargeMode) {
if (rechargeMode == null) {
return null;
}
for (ConsumeRechargeModeEnum way : ConsumeRechargeModeEnum.values()) {
if (way.getValue().equals(rechargeMode)) {
return way;
}
}
return null;
}
default ConsumeRechargeTypeEnum convertRechargeType(Integer rechargeType) {
if (rechargeType == null) {
return null;
}
for (ConsumeRechargeTypeEnum way : ConsumeRechargeTypeEnum.values()) {
if (way.getValue().equals(rechargeType)) {
return way;
}
}
return null;
}
}

View File

@@ -0,0 +1,8 @@
package top.ysoft.admin.consume.mapstruct;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface RecordConvert {
}

View File

@@ -0,0 +1,54 @@
package top.ysoft.admin.consume.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.time.*;
/**
* 消费下发记录实体
*
* @author zc
* @since 2026/02/25 11:01
*/
@Data
@TableName("consume_down_record")
public class DownRecordDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备ID
*/
private Long equipmentId;
/**
* 人员ID
*/
private Long peopleId;
/**
* 下发时间
*/
private LocalDateTime downTime;
/**
* 下发结果0-成功1-失败
*/
private Integer downResult;
/**
* 下发结果报文
*/
private String msg;
/**
* 0:新增 1:修改 2:下发 3删除
*/
private Integer operType;
}

View File

@@ -0,0 +1,47 @@
package top.ysoft.admin.consume.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 消费组实体
*
* @author zc
* @since 2026/01/27 13:48
*/
@Data
@TableName("consume_group")
public class GroupDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消费组名称
*/
private String groupName;
/**
* 补贴金额
*/
private BigDecimal money;
/**
* 部门ID
*/
private String branchId;
/**
* 补贴日期(每月几号)
*/
private String subsidyDate;
/**
* 是否补贴0否1是
*/
private Integer timedConsumption;
}

View File

@@ -0,0 +1,25 @@
package top.ysoft.admin.consume.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 消费组用户关联表
*
* @author zc
* @since 2026/01/27 13:48
*/
@Data
@TableName("consume_group_user")
public class GroupUserDO {
/**
* 消费组ID
*/
private Long groupId;
/**
* 用户ID
*/
private Long userId;
}

View File

@@ -0,0 +1,114 @@
package top.ysoft.admin.consume.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.time.*;
import java.math.BigDecimal;
/**
* 充值记录实体
*
* @author zc
* @since 2026/01/29 16:11
*/
@Data
@TableName("consume_recharge_record")
public class RechargeRecordDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 帐号people_id
*/
private Long accountId;
/**
* 后面A表示工号如果是B表示卡号
*/
private String adminRecharge;
/**
* 内部图片路径
*/
private String base64;
/**
* 消费金额,分
*/
private BigDecimal consumeMoney;
/**
* 折扣金额,分
*/
private BigDecimal discountMoney;
/**
* 工号
*/
private String empId;
/**
* 交易发起时间
*/
private LocalDateTime startTime;
/**
* 交易结束时间
*/
private LocalDateTime endTime;
/**
* 订单类型
*/
private Integer orderType;
/**
* 单号,支付宝和微信使用,平台单号
*/
private String outTradeId;
/**
* 支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码
*/
private Integer payMode;
/**
* 交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费
*/
private Integer resultCode;
/**
* 记录版本1考勤2门禁22人脸机消费25人脸机充值
*/
private Integer ver;
/**
* 充值方式 0现金 1支付宝 2微信 3网银
*/
private Integer rechargeMode;
/**
* 充值来源 0平台 1手机
*/
private Integer rechargeWay;
/**
* 充值类型 0补贴 1充值 2退款
*/
private Integer rechargeType;
/**
* 清除补贴金额,分
*/
private BigDecimal clearBtje;
/**
* 清除充值金额,分
*/
private BigDecimal clearCzje;
}

View File

@@ -0,0 +1,155 @@
package top.ysoft.admin.consume.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.*;
/**
* 消费记录实体
*
* @author zc
* @since 2026/01/21 17:53
*/
@Data
@TableName("consume_record")
public class RecordDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录版本1考勤2门禁22人脸机消费25人脸机充值
*/
private Integer ver;
/**
* 工号
*/
private String empId;
/**
* 帐号
*/
private Long accountId;
/**
* 内部图片路径
*/
private String base64;
/**
* 消费方式0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式
*/
private Integer consumeType;
/**
* 消费金额,分
*/
private BigDecimal consumeMoney;
/**
* 补贴消费金额,分
*/
private BigDecimal subsidyMoney;
/**
* 充值消费金额,分
*/
private BigDecimal rechargeMoney;
/**
* 折扣金额,分
*/
private BigDecimal discountMoney;
/**
* 单号,支付宝和微信使用,平台单号
*/
private String outTradeId;
/**
* 交易发起时间
*/
private String startTime;
/**
* 交易结束时间
*/
private String endTime;
/**
* 交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费
*/
private Integer resultCode;
/**
* 支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码
*/
private Integer payMode;
/**
* 订单类型
*/
private Integer orderType;
/**
* 钱包扣款模式0先消费补贴再个人1仅现金2仅补贴
*/
private Integer walletConsumeMode;
/**
* 多个相似的人脸出现时返回工号最多3个分号隔开
*/
private String likeId;
/**
* 卡号
*/
private String cardSn;
/**
* 交易发起时间
*/
private LocalDateTime startDate;
/**
* 交易结束时间
*/
private LocalDateTime endDate;
/**
* 交易结果
*/
private String consumeResult;
/**
* 备注
*/
private String remark;
/**
* 设备ID
*/
private Long devId;
/**
* 餐段ID
*/
private String timeId;
/**
* 支付宝买家ID
*/
private String buyerId;
/**
* 微信买家ID
*/
private String openid;
}

View File

@@ -0,0 +1,93 @@
package top.ysoft.admin.consume.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
/**
* 商户信息实体
*
* @author zc
* @since 2026/01/30 14:55
*/
@Data
@TableName("consume_tenant")
public class TenantDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 商户名称
*/
private String name;
/**
* 商户类型 0支付宝 1微信 2支付宝+微信 3银行
*/
private Integer type;
/**
* 客户的appid
*/
private String zfbAppId;
/**
* 服务商PID
*/
private String zfbSysServiceProviderId;
/**
* 私有密钥
*/
private String zfbRsaPrivateKey;
/**
* 是否停用
*/
private Long zfbStop;
/**
* 是否测试模式
*/
private Integer zfbMode;
/**
* 公众号appid
*/
private String wxAppid;
/**
* 服务商商户号
*/
private String wxMchId;
/**
* 特约商户号
*/
private String wxSubMchId;
/**
* 支付key
*/
private String wxKey;
/**
* 是否停用
*/
private Integer wxStop;
/**
* 是否测试模式
*/
private Integer wxMode;
/**
* 平台参数ID
*/
private String paramId;
}

View File

@@ -0,0 +1,74 @@
package top.ysoft.admin.consume.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 消费时段实体
*
* @author zc
* @since 2026/01/30 14:56
*/
@Data
@TableName("consume_time_interval")
public class TimeIntervalDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 开始时间
*/
private String startTime;
/**
* 结束时间
*/
private String endTime;
/**
* 刷卡方式 00刷卡 01刷卡+密码
*/
private String useMode;
/**
* 消费餐段 0早餐 1中餐 2午餐 3夜餐
*/
private Integer tmrtype;
/**
* 预留
*/
private String mark;
/**
* 卡级别
*/
private Integer level;
/**
* 最大刷卡次数
*/
private Integer maxcount;
/**
* 定额方式下要扣的金额
*/
private BigDecimal fixmoney;
/**
* 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式
*/
private Integer consumeType;
/**
* 平台参数ID
*/
private String paramId;
}

View File

@@ -0,0 +1,70 @@
package top.ysoft.admin.consume.model.query;
import cn.hutool.core.date.DatePattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.format.annotation.DateTimeFormat;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import java.util.Date;
import java.util.List;
/**
* 消费下发记录查询条件
*
* @author zc
* @since 2026/02/25 11:01
*/
@Data
@Schema(description = "消费下发记录查询条件")
public class DownRecordQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备ID
*/
@Schema(description = "设备ID")
private Long equipmentId;
/**
* 人员名称
*/
@Schema(description = "人员名称")
private String peopleName;
/**
* 下发时间开始
*/
@Schema(description = "下发时间开始")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private Date startTime;
/**
* 下发时间结束
*/
@Schema(description = "下发时间结束")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private Date endTime;
/**
* 下发结果0-成功1-失败
*/
@Schema(description = "下发结果0-成功1-失败")
private Integer downResult;
/**
* 0:新增 1:修改 2:下发 3删除
*/
@Schema(description = "0:新增 1:修改 2:下发 3删除")
private Integer operType;
}

View File

@@ -0,0 +1,33 @@
package top.ysoft.admin.consume.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 消费组查询条件
*
* @author zc
* @since 2026/01/27 13:48
*/
@Data
@Schema(description = "消费组查询条件")
public class GroupQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消费组名称
*/
@Query(type = QueryType.LIKE)
private String groupName;
}

View File

@@ -0,0 +1,58 @@
package top.ysoft.admin.consume.model.query;
import cn.hutool.core.date.DatePattern;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 充值记录查询条件
*
* @author zc
* @since 2026/01/29 16:11
*/
@Data
@Schema(description = "充值记录查询条件")
public class RechargeRecordQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 帐号名称
*/
@Schema(description = "帐号名称")
private String accountName;
/**
* 工号
*/
@Schema(description = "工号")
private String empId;
/**
* 充值类型0补贴 1充值
*/
@Schema(description = "充值类型0补贴 1充值 2退款")
private Integer rechargeType;
/**
* 开始时间
*/
@Schema(description = "开始时间")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private Date startTime;
/**
* 结束时间
*/
@Schema(description = "结束时间")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private Date endTime;
}

View File

@@ -0,0 +1,84 @@
package top.ysoft.admin.consume.model.query;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 消费记录查询条件
*
* @author zc
* @since 2026/01/21 17:53
*/
@Data
@Schema(description = "消费记录查询条件")
public class RecordQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 工号
*/
@Schema(description = "工号")
@Query(type = QueryType.EQ)
private String empId;
/**
* 单号,支付宝和微信使用,平台单号
*/
@Schema(description = "单号,支付宝和微信使用,平台单号")
@Query(type = QueryType.EQ)
private String outTradeId;
/**
* 人员名称
*/
@Schema(description = "人员名称")
private String accountName;
/**
* 交易发起时间
*/
@Schema(description = "交易发起时间")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@Query(type = QueryType.GT)
private Date startDate;
/**
* 交易结束时间
*/
@Schema(description = "交易结束时间")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@Query(type = QueryType.LT)
private Date endDate;
/**
* 交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费
*/
@Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费")
@Query(type = QueryType.EQ)
private Integer resultCode;
/**
* 支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码
*/
@Schema(description = "支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码")
@Query(type = QueryType.EQ)
private Integer payMode;
/**
* 设备ID
*/
@Schema(description = "设备ID")
@Query(type = QueryType.EQ)
private Long devId;
}

View File

@@ -0,0 +1,28 @@
package top.ysoft.admin.consume.model.query;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.util.List;
@Data
@Schema(description = "报表查询条件")
public class ReportQuery {
@Schema(description = "创建时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@Size(max = 2, message = "创建时间必须是一个范围")
private List<Date> createTime;
private String branchId;
private String peopleName;
private String gh;
private Integer tmrtype;
}

View File

@@ -0,0 +1,40 @@
package top.ysoft.admin.consume.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 商户信息查询条件
*
* @author zc
* @since 2026/01/30 14:55
*/
@Data
@Schema(description = "商户信息查询条件")
public class TenantQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 商户类型 0支付宝 1微信 2支付宝+微信 3银行
*/
@Schema(description = "商户类型 0支付宝 1微信 2支付宝+微信 3银行")
@Query(type = QueryType.EQ)
private Integer type;
/**
* 商户名称
*/
@Schema(description = "商户名称")
@Query(type = QueryType.LIKE)
private String name;
}

View File

@@ -0,0 +1,41 @@
package top.ysoft.admin.consume.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 消费时段查询条件
*
* @author zc
* @since 2026/01/30 14:56
*/
@Data
@Schema(description = "消费时段查询条件")
public class TimeIntervalQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 刷卡方式 00刷卡 01刷卡+密码
*/
@Schema(description = "刷卡方式 00刷卡 01刷卡+密码")
@Query(type = QueryType.EQ)
private String useMode;
/**
* 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式
*/
@Schema(description = "消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式")
@Query(type = QueryType.EQ)
private Integer consumeType;
}

View File

@@ -0,0 +1,76 @@
package top.ysoft.admin.consume.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 创建或修改消费下发记录参数
*
* @author zc
* @since 2026/02/25 11:01
*/
@Data
@Schema(description = "创建或修改消费下发记录参数")
public class DownRecordReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备ID
*/
@Schema(description = "设备ID")
@NotNull(message = "设备ID不能为空")
private Long equipmentId;
/**
* 人员ID
*/
@Schema(description = "人员ID")
@NotNull(message = "人员ID不能为空")
private Long peopleId;
/**
* 下发时间
*/
@Schema(description = "下发时间")
@NotNull(message = "下发时间不能为空")
private LocalDateTime downTime;
/**
* 下发结果0-成功1-失败
*/
@Schema(description = "下发结果0-成功1-失败")
@NotNull(message = "下发结果0-成功1-失败不能为空")
private Integer downResult;
/**
* 0:新增 1:修改 2:下发 3删除
*/
@Schema(description = "0:新增 1:修改 2:下发 3删除")
@NotNull(message = "0:新增 1:修改 2:下发 3删除不能为空")
private Integer operType;
/**
* 创建人
*/
@Schema(description = "创建人")
@NotNull(message = "创建人不能为空")
private Long createUser;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@NotNull(message = "创建时间不能为空")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,62 @@
package top.ysoft.admin.consume.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 创建或修改消费组参数
*
* @author zc
* @since 2026/01/27 13:48
*/
@Data
@Schema(description = "创建或修改消费组参数")
public class GroupReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消费组名称
*/
@Schema(description = "消费组名称")
@Length(max = 64, message = "消费组名称长度不能超过64个字符")
private String groupName;
/**
* 补贴金额
*/
@Schema(description = "补贴金额")
private BigDecimal money;
/**
* 补贴日期(每月几号)
*/
@Schema(description = "补贴日期(每月几号)")
private String subsidyDate;
/**
* 是否补贴0否1是
*/
@Schema(description = "是否补贴0否1是")
private Integer timedConsumption;
/**
* 人员ids
*/
@Schema(description = "人员ids")
private List<Long> peopleIds;
/**
* 部门id
*/
@Schema(description = "部门id")
private String branchId;
}

View File

@@ -0,0 +1,47 @@
package top.ysoft.admin.consume.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import top.ysoft.admin.common.enums.DisEnableStatusEnum;
import top.ysoft.admin.system.enums.ImportPolicyEnum;
import java.io.Serial;
import java.io.Serializable;
/**
* 人员充值导入参数
*
* @author zc
* @since 2024-6-17 16:42
*/
@Data
@Schema(description = "人员充值导入参数")
public class PeopleDepositImportReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话KEY
*/
@Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
@NotBlank(message = "导入已过期,请重新上传")
private String importKey;
/**
* 用户重复策略
*/
@Schema(description = "人员不存在策略", example = "1")
@NotNull(message = "人员不存在策略不能为空")
private ImportPolicyEnum peopleNotExist;
/**
* 消费未启用策略
*/
@Schema(description = "消费未启用策略", example = "1")
@NotNull(message = "消费未启用策略不能为空")
private ImportPolicyEnum consumeNotEnable;
}

View File

@@ -0,0 +1,63 @@
package top.ysoft.admin.consume.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import top.ysoft.admin.common.constant.RegexConstants;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 人员充值导入行数据
*
* @author Kils
* @since 2024-6-17 16:42
*/
@Data
@Schema(description = "人员充值导入行数据")
public class PeopleDepositImportRowReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
private String peopleName;
/**
* 工号
*/
@NotBlank(message = "工号不能为空")
private String gh;
/**
* 充值金额
*/
@NotBlank(message = "充值金额不能为空")
@Pattern(regexp = RegexConstants.MONEY, message = "充值金额必须为数字")
private String czje;
/**
* 补贴金额
*/
@NotBlank(message = "补贴金额不能为空")
@Pattern(regexp = RegexConstants.MONEY, message = "补贴金额必须为数字")
private String btje;
/**
* 是否启用消费
*/
@NotBlank(message = "是否启用消费不能为空")
private String isConsume;
/**
* 是否冻结
*/
@NotBlank(message = "是否冻结不能为空")
private String freeze;
}

View File

@@ -0,0 +1,100 @@
package top.ysoft.admin.consume.model.req;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 创建或修改充值记录参数
*
* @author zc
* @since 2026/01/29 16:11
*/
@Data
@Schema(description = "创建或修改充值记录参数")
public class RechargeRecordReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 帐号people_id
*/
@Schema(description = "帐号people_id")
private Long accountId;
/**
* 后面A表示工号如果是B表示卡号
*/
@Schema(description = "后面A表示工号如果是B表示卡号")
@Length(max = 50, message = "后面A表示工号如果是B表示卡号长度不能超过 {max} 个字符")
private String adminRecharge;
/**
* 消费金额,分
*/
@Schema(description = "消费金额,分")
private BigDecimal consumeMoney;
/**
* 折扣金额,分
*/
@Schema(description = "折扣金额,分")
private BigDecimal discountMoney;
/**
* 工号
*/
@Schema(description = "工号")
@Length(max = 50, message = "工号长度不能超过 {max} 个字符")
private String empId;
/**
* 订单类型
*/
@Schema(description = "订单类型")
private Integer orderType;
/**
* 单号,支付宝和微信使用,平台单号
*/
@Schema(description = "单号,支付宝和微信使用,平台单号")
@Length(max = 50, message = "单号,支付宝和微信使用,平台单号长度不能超过 {max} 个字符")
private String outTradeId;
/**
* 支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码
*/
@Schema(description = "支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码")
private Integer payMode;
/**
* 交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费
*/
@Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费")
private Integer resultCode;
/**
* 充值方式
*/
@Schema(description = "充值方式")
private Integer rechargeMode;
/**
* 充值来源
*/
@Schema(description = "充值来源")
private Integer rechargeWay;
/**
* 充值类型
*/
@Schema(description = "充值类型")
private Integer rechargeType;
}

View File

@@ -0,0 +1,23 @@
package top.ysoft.admin.consume.model.req;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 创建或修改消费记录参数
*
* @author zc
* @since 2026/01/21 17:53
*/
@Data
@Schema(description = "创建或修改消费记录参数")
public class RecordReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,112 @@
package top.ysoft.admin.consume.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
/**
* 创建或修改商户信息参数
*
* @author zc
* @since 2026/01/30 14:55
*/
@Data
@Schema(description = "创建或修改商户信息参数")
public class TenantReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 商户名称
*/
@Schema(description = "商户名称")
@Length(max = 50, message = "商户名称长度不能超过50个字符")
private String name;
/**
* 商户类型 0支付宝 1微信 2支付宝+微信 3银行
*/
@Schema(description = "商户类型 0支付宝 1微信 2支付宝+微信 3银行")
private Integer type;
/**
* 客户的appid
*/
@Schema(description = "客户的appid")
@Length(max = 50, message = "客户的appid长度不能超过50个字符")
private String zfbAppId;
/**
* 服务商PID
*/
@Schema(description = "服务商PID")
@Length(max = 50, message = "服务商PID长度不能超过50个字符")
private String zfbSysServiceProviderId;
/**
* 私有密钥
*/
@Schema(description = "私有密钥")
private String zfbRsaPrivateKey;
/**
* 是否停用
*/
@Schema(description = "是否停用")
private Long zfbStop;
/**
* 是否测试模式
*/
@Schema(description = "是否测试模式")
private Integer zfbMode;
/**
* 公众号appid
*/
@Schema(description = "公众号appid")
@Length(max = 50, message = "公众号appid长度不能超过50个字符")
private String wxAppid;
/**
* 服务商商户号
*/
@Schema(description = "服务商商户号")
@Length(max = 50, message = "服务商商户号长度不能超过50个字符")
private String wxMchId;
/**
* 特约商户号
*/
@Schema(description = "特约商户号")
@Length(max = 50, message = "特约商户号长度不能超过50个字符")
private String wxSubMchId;
/**
* 支付key
*/
@Schema(description = "支付key")
@Length(max = 50, message = "支付key长度不能超过50个字符")
private String wxKey;
/**
* 是否停用
*/
@Schema(description = "是否停用")
private Integer wxStop;
/**
* 是否测试模式
*/
@Schema(description = "是否测试模式")
private Integer wxMode;
/**
* 平台参数ID
*/
@Schema(description = "平台参数ID")
private String paramId;
}

View File

@@ -0,0 +1,58 @@
package top.ysoft.admin.consume.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 创建或修改消费时段参数
*
* @author zc
* @since 2026/01/30 14:56
*/
@Data
@Schema(description = "创建或修改消费时段参数")
public class TimeIntervalReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 开始时间
*/
private String startTime;
/**
* 结束时间
*/
private String endTime;
/**
* 刷卡方式 00刷卡 01刷卡+密码
*/
private String useMode;
/**
* 最大刷卡次数
*/
private Integer maxcount;
/**
* 定额方式下要扣的金额
*/
private BigDecimal fixmoney;
/**
* 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式
*/
private Integer consumeType;
/**
* 消费餐段 0早餐 1中餐 2午餐 3夜餐
*/
private Integer tmrtype;
}

View File

@@ -0,0 +1,78 @@
package top.ysoft.admin.consume.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 消费下发记录信息
*
* @author zc
* @since 2026/02/25 11:01
*/
@Data
@Schema(description = "消费下发记录信息")
public class DownRecordResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备ID
*/
@Schema(description = "设备ID")
private Long equipmentId;
/**
* 设备名称
*/
@Schema(description = "设备名称")
private String equipmentName;
/**
* 人员ID
*/
@Schema(description = "人员ID")
private Long peopleId;
/**
* 人员名称
*/
@Schema(description = "人员名称")
private String peopleName;
/**
* 工号
*/
@Schema(description = "工号")
private String gh;
/**
* 下发时间
*/
@Schema(description = "下发时间")
private LocalDateTime downTime;
/**
* 下发结果0-成功1-失败
*/
@Schema(description = "下发结果0-成功1-失败")
private Integer downResult;
/**
* 下发结果报文
*/
@Schema(description = "下发结果报文")
private String msg;
/**
* 0:新增 1:修改 2:下发 3删除
*/
@Schema(description = "0:新增 1:修改 2:下发 3删除")
private Integer operType;
}

View File

@@ -0,0 +1,64 @@
package top.ysoft.admin.consume.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.*;
import java.util.List;
/**
* 消费组信息
*
* @author zc
* @since 2026/01/27 13:48
*/
@Data
@Schema(description = "消费组信息")
public class GroupResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消费组名称
*/
@Schema(description = "消费组名称")
private String groupName;
/**
* 补贴金额
*/
@Schema(description = "补贴金额")
private BigDecimal money;
/**
* 补贴日期
*/
@Schema(description = "补贴日期(每月几号)")
private String subsidyDate;
/**
* 是否补贴
*/
@Schema(description = "是否补贴")
private Integer timedConsumption;
/**
* 人员ids
*/
@Schema(description = "人员ids")
private List<Long> peopleIds;
/**
* 人员下拉数据
*/
@Schema(description = "人员下拉数据")
private List<LabelValueResp> peopleSelect;
}

View File

@@ -0,0 +1,53 @@
package top.ysoft.admin.consume.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 人员充值导入解析结果
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "人员充值导入解析结果")
public class PeopleDepositImportParseResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话 Key
*/
@Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
private String importKey;
/**
* 总计行数
*/
@Schema(description = "总计行数", example = "100")
private Integer totalRows;
/**
* 有效行数
*/
@Schema(description = "有效行数", example = "100")
private Integer validRows;
/**
* 重复行数
*/
@Schema(description = "重复行数", example = "100")
private Integer duplicateUserRows;
/**
* 消费未启用行数
*/
@Schema(description = "消费未启用行数", example = "100")
private Integer unEnabledRows;
}

View File

@@ -0,0 +1,171 @@
package top.ysoft.admin.consume.model.resp;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter;
import top.ysoft.admin.common.enums.ConsumeRechargeModeEnum;
import top.ysoft.admin.common.enums.ConsumeRechargeTypeEnum;
import top.ysoft.admin.common.enums.ConsumeRechargeWayEnum;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 充值记录信息
*
* @author zc
* @since 2026/01/29 16:11
*/
@Data
@Schema(description = "充值记录信息")
public class RechargeRecordResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 帐号people_id
*/
@Schema(description = "帐号people_id")
@ExcelIgnore
private Long accountId;
/**
* 人员名称
*/
@Schema(description = "人员名称")
@ExcelProperty(value = "人员名称", order = 2)
private String accountName;
/**
* 后面A表示工号如果是B表示卡号
*/
@Schema(description = "后面A表示工号如果是B表示卡号")
@ExcelIgnore
private String adminRecharge;
/**
* 内部图片路径
*/
@Schema(description = "内部图片路径")
@ExcelIgnore
private String base64;
/**
* 消费金额,分
*/
@Schema(description = "充值金额,分")
@ExcelProperty(value = "操作金额(分)", order = 3)
private BigDecimal consumeMoney;
/**
* 折扣金额,分
*/
@Schema(description = "折扣金额,分")
@ExcelIgnore
private BigDecimal discountMoney;
/**
* 工号
*/
@Schema(description = "工号")
@ExcelProperty(value = "工号", order = 1)
private String empId;
/**
* 交易发起时间
*/
@Schema(description = "交易发起时间")
@ExcelIgnore
private LocalDateTime startTime;
/**
* 交易结束时间
*/
@Schema(description = "交易结束时间")
@ExcelIgnore
private LocalDateTime endTime;
/**
* 订单类型
*/
@Schema(description = "订单类型")
@ExcelIgnore
private Integer orderType;
/**
* 单号,支付宝和微信使用,平台单号
*/
@Schema(description = "单号,支付宝和微信使用,平台单号")
@ExcelIgnore
private String outTradeId;
/**
* 支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码
*/
@Schema(description = "支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码")
@ExcelIgnore
private Integer payMode;
/**
* 交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费
*/
@Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费")
@ExcelIgnore
private Integer resultCode;
/**
* 记录版本1考勤2门禁22人脸机消费25人脸机充值
*/
@Schema(description = "记录版本1考勤2门禁22人脸机消费25人脸机充值")
@ExcelIgnore
private Integer ver;
/**
* 充值方式
*/
@Schema(description = "充值方式 0现金 1支付宝 2微信 3网银")
@ExcelProperty(value = "充值方式", converter = ExcelBaseEnumConverter.class, order = 5)
private ConsumeRechargeModeEnum rechargeMode;
/**
* 充值来源
*/
@Schema(description = "充值来源 0平台 1手机")
@ExcelProperty(value = "充值来源", converter = ExcelBaseEnumConverter.class, order = 6)
private ConsumeRechargeWayEnum rechargeWay;
/**
* 充值类型
*/
@Schema(description = "充值类型 0补贴 1充值")
@ExcelProperty(value = "充值类型", converter = ExcelBaseEnumConverter.class, order = 4)
private ConsumeRechargeTypeEnum rechargeType;
/**
* 创建时间
*/
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
@ExcelProperty(value = "创建时间", order = 7)
@ColumnWidth(30)
private LocalDateTime createTime;
/**
* 清除补贴金额,分
*/
@Schema(description = "清除补贴金额,分")
@ExcelIgnore
private BigDecimal clearBtje;
/**
* 清除充值金额,分
*/
@Schema(description = "清除充值金额,分")
@ExcelIgnore
private BigDecimal clearCzje;
}

View File

@@ -0,0 +1,247 @@
package top.ysoft.admin.consume.model.resp;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter;
import top.ysoft.admin.common.enums.*;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 消费记录信息
*
* @author zc
* @since 2026/01/21 17:53
*/
@Data
@Schema(description = "消费记录信息")
public class RecordResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录版本1考勤2门禁22人脸机消费25人脸机充值
*/
@Schema(description = "记录版本1考勤2门禁22人脸机消费25人脸机充值")
@ExcelIgnore
private Integer ver;
/**
* 工号
*/
@Schema(description = "工号")
@ExcelProperty(value = "工号", order = 1)
private String empId;
/**
* 帐号
*/
@Schema(description = "帐号")
@ExcelIgnore
private Long accountId;
/**
* 人员名称
*/
@Schema(description = "人员名称")
@ExcelProperty(value = "人员", order = 2)
private String accountName;
/**
* 内部图片路径
*/
@Schema(description = "内部图片路径")
@ExcelIgnore
private String base64;
/**
* 消费方式0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式
*/
@Schema(description = "消费方式0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式")
@ExcelProperty(value = "消费方式", converter = ExcelBaseEnumConverter.class, order = 4)
private ConsumeTypeConverter consumeType;
/**
* 消费金额,分
*/
@Schema(description = "消费总金额,分")
@ExcelProperty(value = "消费总金额", order = 5)
private BigDecimal consumeMoney = BigDecimal.ZERO;
/**
* 补贴消费金额,分
*/
@Schema(description = "补贴消费金额,分")
@ExcelProperty(value = "补贴消费金额", order = 6)
private BigDecimal subsidyMoney = BigDecimal.ZERO;
/**
* 充值消费金额,分
*/
@Schema(description = "充值消费金额,分")
@ExcelProperty(value = "充值消费金额", order = 7)
private BigDecimal rechargeMoney = BigDecimal.ZERO;
/**
* 折扣金额,分
*/
@Schema(description = "折扣金额,分")
@ExcelIgnore
private BigDecimal discountMoney = BigDecimal.ZERO;
/**
* 单号,支付宝和微信使用,平台单号
*/
@Schema(description = "单号,支付宝和微信使用,平台单号")
@ExcelProperty(value = "单号", order = 10)
private String outTradeId;
/**
* 交易发起时间
*/
@Schema(description = "交易发起时间")
@ExcelIgnore
private String startTime;
/**
* 交易结束时间
*/
@Schema(description = "交易结束时间")
@ExcelIgnore
private String endTime;
/**
* 交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费
*/
@Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常4异常消费")
@ExcelProperty(value = "交易状态", converter = ExcelBaseEnumConverter.class, order = 11)
private ConsumeResultEnum resultCode;
/**
* 支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码
*/
@Schema(description = "支付类型0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码")
@ExcelProperty(value = "支付类型", converter = ExcelBaseEnumConverter.class, order = 12)
private ConsumePayModeEnum payMode;
/**
* 订单类型
*/
@Schema(description = "订单类型")
@ExcelProperty(value = "订单类型", converter = ExcelBaseEnumConverter.class, order = 13)
private ConsumeOrderTypeEnum orderType;
/**
* 钱包扣款模式0先消费补贴再个人1仅现金2仅补贴
*/
@Schema(description = "钱包扣款模式0先消费补贴再个人1仅现金2仅补贴")
@ExcelIgnore
private ConsumeWalletModeEnum walletConsumeMode;
/**
* 多个相似的人脸出现时返回工号最多3个分号隔开
*/
@Schema(description = "多个相似的人脸出现时返回工号最多3个分号隔开")
@ExcelIgnore
private String likeId;
/**
* 卡号
*/
@Schema(description = "卡号")
@ExcelIgnore
private String cardSn;
/**
* 交易发起时间
*/
@Schema(description = "交易发起时间")
@ExcelIgnore
private LocalDateTime startDate;
/**
* 交易结束时间
*/
@Schema(description = "交易结束时间")
@ExcelIgnore
private LocalDateTime endDate;
/**
* 交易结果
*/
@Schema(description = "交易结果")
@ExcelIgnore
private String consumeResult;
/**
* 备注
*/
@Schema(description = "备注")
@ExcelIgnore
private String remark;
/**
* 设备ID
*/
@Schema(description = "设备ID")
@ExcelIgnore
private Long devId;
/**
* 设备名称
*/
@Schema(description = "设备名称")
@ExcelProperty(value = "设备名称", order = 3)
private String equipmentName;
/**
* 餐段ID
*/
@Schema(description = "餐段ID")
@ExcelIgnore
private String timeId;
/**
* 支付宝买家ID
*/
@Schema(description = "支付宝买家ID")
@ExcelIgnore
private String buyerId;
/**
* 微信买家ID
*/
@Schema(description = "微信买家ID")
@ExcelIgnore
private String openid;
/**
* 充值金额
*/
@Schema(description = "充值金额")
@ExcelProperty(value = "充值金额", order = 9)
private BigDecimal czje = BigDecimal.ZERO;
/**
* 补贴金额
*/
@Schema(description = "补贴金额")
@ExcelProperty(value = "补贴金额", order = 8)
private BigDecimal btje = BigDecimal.ZERO;
/**
* 创建时间
*/
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
@ExcelProperty(value = "创建时间", order = 14)
@ColumnWidth(30)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,120 @@
package top.ysoft.admin.consume.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 商户信息信息
*
* @author zc
* @since 2026/01/30 14:55
*/
@Data
@Schema(description = "商户信息信息")
public class TenantResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 商户名称
*/
@Schema(description = "商户名称")
private String name;
/**
* 商户类型
*/
@Schema(description = "商户类型")
private String type;
/**
* 客户的appid
*/
@Schema(description = "客户的appid")
private String zfbAppId;
/**
* 服务商PID
*/
@Schema(description = "服务商PID")
private String zfbSysServiceProviderId;
/**
* 私有密钥
*/
@Schema(description = "私有密钥")
private String zfbRsaPrivateKey;
/**
* 是否停用
*/
@Schema(description = "是否停用")
private String zfbStop;
/**
* 是否测试模式
*/
@Schema(description = "是否测试模式")
private String zfbMode;
/**
* 公众号appid
*/
@Schema(description = "公众号appid")
private String wxAppid;
/**
* 服务商商户号
*/
@Schema(description = "服务商商户号")
private String wxMchId;
/**
* 特约商户号
*/
@Schema(description = "特约商户号")
private String wxSubMchId;
/**
* 支付key
*/
@Schema(description = "支付key")
private String wxKey;
/**
* 是否停用
*/
@Schema(description = "是否停用")
private String wxStop;
/**
* 是否测试模式
*/
@Schema(description = "是否测试模式")
private String wxMode;
/**
* 平台参数ID
*/
@Schema(description = "平台参数ID")
private String paramId;
/**
* 更新者
*/
@Schema(description = "更新者")
private Long updateUser;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,97 @@
package top.ysoft.admin.consume.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
import java.math.BigDecimal;
/**
* 消费时段信息
*
* @author zc
* @since 2026/01/30 14:56
*/
@Data
@Schema(description = "消费时段信息")
public class TimeIntervalResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 开始时间
*/
@Schema(description = "开始时间")
private String startTime;
/**
* 结束时间
*/
@Schema(description = "结束时间")
private String endTime;
/**
* 刷卡方式 00刷卡 01刷卡+密码
*/
@Schema(description = "刷卡方式 00刷卡 01刷卡+密码")
private String useMode;
/**
* 消费餐段 0早餐 1中餐 2午餐 3夜餐
*/
@Schema(description = "消费餐段 0早餐 1中餐 2午餐 3夜餐")
private String tmrtype;
/**
* 预留
*/
@Schema(description = "预留")
private String mark;
/**
* 卡级别
*/
@Schema(description = "卡级别")
private Integer level;
/**
* 最大刷卡次数
*/
@Schema(description = "最大刷卡次数")
private Integer maxcount;
/**
* 定额方式下要扣的金额
*/
@Schema(description = "定额方式下要扣的金额")
private BigDecimal fixmoney;
/**
* 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式
*/
@Schema(description = "消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式")
private String consumeType;
/**
* 平台参数ID
*/
@Schema(description = "平台参数ID")
private String paramId;
/**
* 更新者
*/
@Schema(description = "更新者")
private Long updateUser;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,73 @@
package top.ysoft.admin.consume.model.resp.report;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Schema(description = "基础报表")
public class BaseReport {
/**
* 合计次数
*/
@ExcelProperty(value = "合计次数", order = Integer.MAX_VALUE - 10)
private Integer totalCount = 0;
/**
* 合计金额
*/
@ExcelProperty(value = "合计金额", order = Integer.MAX_VALUE - 9)
private BigDecimal totalAmount = BigDecimal.ZERO;
/**
* 单价次数
*/
@ExcelProperty(value = "单价次数", order = Integer.MAX_VALUE - 8)
private Integer unitPriceCount = 0;
/**
* 单价金额
*/
@ExcelProperty(value = "单价金额", order = Integer.MAX_VALUE - 7)
private BigDecimal unitPriceAmount = BigDecimal.ZERO;
/**
* 定值次数
*/
@ExcelProperty(value = "定值次数", order = Integer.MAX_VALUE - 6)
private Integer fixedValueCount = 0;
/**
* 定值金额
*/
@ExcelProperty(value = "定值金额", order = Integer.MAX_VALUE - 5)
private BigDecimal fixedValueAmount = BigDecimal.ZERO;
/**
* 计次次数
*/
@ExcelProperty(value = "计次次数", order = Integer.MAX_VALUE - 4)
private Integer countingCount = 0;
/**
* 计次金额
*/
@ExcelProperty(value = "计次金额", order = Integer.MAX_VALUE - 3)
private BigDecimal countingAmount = BigDecimal.ZERO;
/**
* 二维码次数
*/
@ExcelProperty(value = "二维码次数", order = Integer.MAX_VALUE - 2)
private Integer qrcodeCount = 0;
/**
* 二维码金额
*/
@ExcelProperty(value = "二维码金额", order = Integer.MAX_VALUE - 1)
private BigDecimal qrcodeAmount = BigDecimal.ZERO;
}

View File

@@ -0,0 +1,20 @@
package top.ysoft.admin.consume.model.resp.report;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 部门报表
*/
@Data
@Schema(description = "部门报表")
public class BranchReport extends BaseReport {
/**
* 部门名称
*/
@ExcelProperty(value = "部门名称", order = Integer.MAX_VALUE - 11)
private String branchName;
}

View File

@@ -0,0 +1,35 @@
package top.ysoft.admin.consume.model.resp.report;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter;
import top.ysoft.admin.common.enums.ConsumeTmrtypeEnum;
/**
* 餐段报表
*/
@Data
@Schema(description = "餐段报表")
public class MealReport extends BaseReport {
/**
* 商户名称
*/
@ExcelProperty(value = "商户名称", order = Integer.MAX_VALUE - 13)
private String merchant;
/**
* 设备名称
*/
@ExcelProperty(value = "设备名称", order = Integer.MAX_VALUE - 12)
private String equipmentName;
/**
* 消费餐段 0早餐 1中餐 2午餐 3夜餐
*/
@ExcelProperty(value = "消费餐段", converter = ExcelBaseEnumConverter.class, order = Integer.MAX_VALUE - 11)
private ConsumeTmrtypeEnum tmrtype;
}

View File

@@ -0,0 +1,28 @@
package top.ysoft.admin.consume.model.resp.report;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 商户报表
*/
@Data
@Schema(description = "商户报表")
public class MerchantReport extends BaseReport {
/**
* 商户名称
*/
@Schema(description = "商户名称")
@ExcelProperty(value = "商户名称", order = Integer.MAX_VALUE - 12)
private String merchant;
/**
* 设备名称
*/
@Schema(description = "设备名称")
@ExcelProperty(value = "设备名称", order = Integer.MAX_VALUE - 11)
private String equipmentName;
}

View File

@@ -0,0 +1,32 @@
package top.ysoft.admin.consume.model.resp.report;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 人员报表
*/
@Data
@Schema(description = "人员报表")
public class PeopleReport extends BaseReport {
/**
* 人员名称
*/
@ExcelProperty(value = "人员名称", order = Integer.MAX_VALUE - 13)
private String peopleName;
/**
* 人员工号
*/
@ExcelProperty(value = "人员工号", order = Integer.MAX_VALUE - 12)
private String gh;
/**
* 部门名称
*/
@ExcelProperty(value = "部门名称", order = Integer.MAX_VALUE - 11)
private String branchName;
}

View File

@@ -0,0 +1,17 @@
package top.ysoft.admin.consume.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.consume.model.query.DownRecordQuery;
import top.ysoft.admin.consume.model.req.DownRecordReq;
import top.ysoft.admin.consume.model.resp.DownRecordResp;
/**
* 消费下发记录业务接口
*
* @author zc
* @since 2026/02/25 11:01
*/
public interface DownRecordService extends BaseService<DownRecordResp, DownRecordResp, DownRecordQuery, DownRecordReq> {
void down(Long equipmentId, Long peopleId);
}

View File

@@ -0,0 +1,14 @@
package top.ysoft.admin.consume.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.consume.model.query.GroupQuery;
import top.ysoft.admin.consume.model.req.GroupReq;
import top.ysoft.admin.consume.model.resp.GroupResp;
/**
* 消费组业务接口
*
* @author zc
* @since 2026/01/27 13:48
*/
public interface GroupService extends BaseService<GroupResp, GroupResp, GroupQuery, GroupReq> {}

View File

@@ -0,0 +1,8 @@
package top.ysoft.admin.consume.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.ysoft.admin.consume.model.entity.GroupUserDO;
public interface GroupUserService extends IService<GroupUserDO> {
}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.consume.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.consume.model.query.RechargeRecordQuery;
import top.ysoft.admin.consume.model.req.RechargeRecordReq;
import top.ysoft.admin.consume.model.resp.RechargeRecordResp;
/**
* 充值记录业务接口
*
* @author zc
* @since 2026/01/29 16:11
*/
public interface RechargeRecordService extends BaseService<RechargeRecordResp, RechargeRecordResp, RechargeRecordQuery, RechargeRecordReq> {
}

View File

@@ -0,0 +1,14 @@
package top.ysoft.admin.consume.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.consume.model.query.RecordQuery;
import top.ysoft.admin.consume.model.req.RecordReq;
import top.ysoft.admin.consume.model.resp.RecordResp;
/**
* 消费记录业务接口
*
* @author zc
* @since 2026/01/21 17:53
*/
public interface RecordService extends BaseService<RecordResp, RecordResp, RecordQuery, RecordReq> {}

View File

@@ -0,0 +1,77 @@
package top.ysoft.admin.consume.service;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.ysoft.admin.consume.model.query.ReportQuery;
import top.ysoft.admin.consume.model.resp.report.BranchReport;
import top.ysoft.admin.consume.model.resp.report.MealReport;
import top.ysoft.admin.consume.model.resp.report.MerchantReport;
import top.ysoft.admin.consume.model.resp.report.PeopleReport;
import java.util.List;
public interface ReportService {
/**
* 查询商户报表
*
* @param query 查询参数
* @return 商户报表列表
*/
List<MerchantReport> queryMerchantReport(ReportQuery query);
/**
* 查询餐段报表
*
* @param query 查询参数
* @return 餐段报表列表
*/
List<MealReport> queryMealReport(ReportQuery query);
/**
* 查询部门报表
*
* @param query 查询参数
* @return 部门报表列表
*/
List<BranchReport> queryBranchReport(ReportQuery query);
/**
* 查询人员报表
*
* @param query 查询参数
* @param pageQuery 分页查询参数
* @return 人员报表分页列表
*/
PageResp<PeopleReport> page(@Param("query") ReportQuery query, @Param("pageQuery") PageQuery pageQuery);
/**
* 导出商户报表
*
* @param query 查询参数
*/
void exportMerchant(ReportQuery query, HttpServletResponse response);
/**
* 导出餐段报表
*
* @param query 查询参数
*/
void exportMeal(ReportQuery query, HttpServletResponse response);
/**
* 导出部门报表
*
* @param query 查询参数
*/
void exportBranch(ReportQuery query, HttpServletResponse response);
/**
* 导出人员报表
*
* @param query 查询参数
*/
void exportPeople(ReportQuery query, HttpServletResponse response);
}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.consume.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.consume.model.query.TenantQuery;
import top.ysoft.admin.consume.model.req.TenantReq;
import top.ysoft.admin.consume.model.resp.TenantResp;
/**
* 商户信息业务接口
*
* @author zc
* @since 2026/01/30 14:55
*/
public interface TenantService extends BaseService<TenantResp, TenantResp, TenantQuery, TenantReq> {
}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.consume.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.consume.model.query.TimeIntervalQuery;
import top.ysoft.admin.consume.model.req.TimeIntervalReq;
import top.ysoft.admin.consume.model.resp.TimeIntervalResp;
/**
* 消费时段业务接口
*
* @author zc
* @since 2026/01/30 14:56
*/
public interface TimeIntervalService extends BaseService<TimeIntervalResp, TimeIntervalResp, TimeIntervalQuery, TimeIntervalReq> {
}

View File

@@ -0,0 +1,62 @@
package top.ysoft.admin.consume.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.ysoft.admin.consume.mapper.DownRecordMapper;
import top.ysoft.admin.consume.model.entity.DownRecordDO;
import top.ysoft.admin.consume.model.entity.RecordDO;
import top.ysoft.admin.consume.model.query.DownRecordQuery;
import top.ysoft.admin.consume.model.query.RecordQuery;
import top.ysoft.admin.consume.model.req.DownRecordReq;
import top.ysoft.admin.consume.model.resp.DownRecordResp;
import top.ysoft.admin.consume.model.resp.RecordResp;
import top.ysoft.admin.consume.service.DownRecordService;
import top.ysoft.admin.system.mapper.PeopleEquipmentMapper;
import top.ysoft.admin.system.model.entity.PeopleEquipmentDO;
/**
* 消费下发记录业务实现
*
* @author zc
* @since 2026/02/25 11:01
*/
@Service
@RequiredArgsConstructor
public class DownRecordServiceImpl extends BaseServiceImpl<DownRecordMapper, DownRecordDO, DownRecordResp, DownRecordResp, DownRecordQuery, DownRecordReq> implements DownRecordService {
private final PeopleEquipmentMapper peopleEquipmentMapper;
@Override
public PageResp<DownRecordResp> page(DownRecordQuery query, PageQuery pageQuery) {
QueryWrapper<DownRecordDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getPeopleName()), "p.name", query.getPeopleName());
queryWrapper.eq(null != query.getDownResult(), "r.down_result", query.getDownResult());
queryWrapper.eq(null != query.getOperType(), "r.oper_type", query.getOperType());
queryWrapper.eq(null != query.getEquipmentId(), "r.equipment_id", query.getEquipmentId());
queryWrapper.ge(null != query.getStartTime(), "r.down_time", query.getStartTime());
queryWrapper.le(null != query.getEndTime(), "r.down_time", query.getEndTime());
this.sort(queryWrapper, pageQuery);
IPage<DownRecordResp> page = baseMapper.selectRecordsPage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}
@Override
public void down(Long equipmentId, Long peopleId) {
peopleEquipmentMapper.delete(new QueryWrapper<PeopleEquipmentDO>()
.eq("equipment_id", equipmentId)
.eq("people_id", peopleId));
}
}

View File

@@ -0,0 +1,96 @@
package top.ysoft.admin.consume.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.ysoft.admin.consume.mapper.GroupMapper;
import top.ysoft.admin.consume.model.entity.GroupDO;
import top.ysoft.admin.consume.model.entity.GroupUserDO;
import top.ysoft.admin.consume.model.query.GroupQuery;
import top.ysoft.admin.consume.model.req.GroupReq;
import top.ysoft.admin.consume.model.resp.GroupResp;
import top.ysoft.admin.consume.service.GroupService;
import top.ysoft.admin.consume.service.GroupUserService;
import top.ysoft.admin.peopleBranch.mapper.PeopleMapper;
import top.ysoft.admin.peopleBranch.mapstruct.PeopleConvert;
import top.ysoft.admin.peopleBranch.model.entity.PeopleDO;
import java.util.List;
import java.util.stream.Collectors;
/**
* 消费组业务实现
*
* @author zc
* @since 2026/01/27 13:48
*/
@Service
@RequiredArgsConstructor
public class GroupServiceImpl extends BaseServiceImpl<GroupMapper, GroupDO, GroupResp, GroupResp, GroupQuery, GroupReq> implements GroupService {
private final GroupUserService groupUserService;
private final PeopleMapper peopleMapper;
private final PeopleConvert peopleConvert;
@Override
public GroupResp get(Long id) {
GroupDO entity = baseMapper.selectById(id);
GroupResp detail = BeanUtil.toBean(entity, GroupResp.class);
List<GroupUserDO> groupUserDOS = groupUserService.list(new LambdaQueryWrapper<GroupUserDO>()
.eq(GroupUserDO::getGroupId, id));
if (CollUtil.isNotEmpty(groupUserDOS)) {
detail.setPeopleIds(groupUserDOS.stream().map(GroupUserDO::getUserId).toList());
//返回人员下拉数据
List<PeopleDO> peopleDOS = peopleMapper.selectByIds(detail.getPeopleIds());
if (CollUtil.isNotEmpty(peopleDOS)) {
detail.setPeopleSelect(peopleConvert.labelValueList(peopleDOS));
}
}
this.fill(detail);
return detail;
}
@Override
public void afterAdd(GroupReq req, GroupDO groupDO) {
if (CollUtil.isNotEmpty(req.getPeopleIds())) {
List<GroupUserDO> groupUserDOS = req.getPeopleIds().stream().map(userId -> {
GroupUserDO groupUserDO = new GroupUserDO();
groupUserDO.setGroupId(groupDO.getId());
groupUserDO.setUserId(userId);
return groupUserDO;
}).collect(Collectors.toList());
groupUserService.saveBatch(groupUserDOS);
}
}
@Override
public void afterUpdate(GroupReq req, GroupDO groupDO) {
if (CollUtil.isNotEmpty(req.getPeopleIds())) {
List<GroupUserDO> groupUserDOS = req.getPeopleIds().stream().map(userId -> {
GroupUserDO groupUserDO = new GroupUserDO();
groupUserDO.setGroupId(groupDO.getId());
groupUserDO.setUserId(userId);
return groupUserDO;
}).collect(Collectors.toList());
groupUserService.remove(new LambdaQueryWrapper<GroupUserDO>().eq(GroupUserDO::getGroupId, groupDO.getId()));
groupUserService.saveBatch(groupUserDOS);
}
}
@Override
public void afterDelete(List<Long> ids) {
if (CollUtil.isNotEmpty(ids)) {
groupUserService.remove(new LambdaQueryWrapper<GroupUserDO>().in(GroupUserDO::getGroupId, ids));
}
}
}

View File

@@ -0,0 +1,20 @@
package top.ysoft.admin.consume.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.ysoft.admin.consume.mapper.GroupUserMapper;
import top.ysoft.admin.consume.model.entity.GroupUserDO;
import top.ysoft.admin.consume.service.GroupUserService;
/**
* 消费组用户业务实现
*
* @author zc
* @since 2026/01/27 13:48
*/
@Service
@RequiredArgsConstructor
public class GroupUserServiceImpl extends ServiceImpl<GroupUserMapper, GroupUserDO> implements GroupUserService {
}

View File

@@ -0,0 +1,72 @@
package top.ysoft.admin.consume.service.impl;
import cn.crane4j.annotation.AutoOperate;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.file.excel.util.ExcelUtils;
import top.ysoft.admin.consume.mapper.RechargeRecordMapper;
import top.ysoft.admin.consume.model.entity.RechargeRecordDO;
import top.ysoft.admin.consume.model.query.RechargeRecordQuery;
import top.ysoft.admin.consume.model.req.RechargeRecordReq;
import top.ysoft.admin.consume.model.resp.RechargeRecordResp;
import top.ysoft.admin.consume.service.RechargeRecordService;
import java.util.List;
/**
* 充值记录业务实现
*
* @author zc
* @since 2026/01/29 16:11
*/
@Service
@RequiredArgsConstructor
public class RechargeRecordServiceImpl extends BaseServiceImpl<RechargeRecordMapper, RechargeRecordDO, RechargeRecordResp, RechargeRecordResp, RechargeRecordQuery, RechargeRecordReq> implements RechargeRecordService {
@Override
@AutoOperate(type = RechargeRecordResp.class, on = "list")
public PageResp<RechargeRecordResp> page(RechargeRecordQuery query, PageQuery pageQuery) {
QueryWrapper<RechargeRecordDO> queryWrapper = buildQueryWrapper(query);
this.sort(queryWrapper, pageQuery);
IPage<RechargeRecordResp> page = baseMapper.selectRechargePage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}
@Override
public void export(RechargeRecordQuery query, SortQuery sortQuery, HttpServletResponse response) {
QueryWrapper<RechargeRecordDO> queryWrapper = buildQueryWrapper(query);
this.sort(queryWrapper, sortQuery);
List<RechargeRecordResp> rechargeRecordResps = baseMapper.selectExport(queryWrapper);
ExcelUtils.export(rechargeRecordResps, "充值记录_" + DateUtil.today(), RechargeRecordResp.class, response);
}
public QueryWrapper<RechargeRecordDO> buildQueryWrapper(RechargeRecordQuery query) {
QueryWrapper<RechargeRecordDO> queryWrapper = new QueryWrapper<>();
//根据人员名称查询
queryWrapper.like(StrUtil.isNotBlank(query.getAccountName()), "p.name", query.getAccountName());
//根据工号查询
queryWrapper.like(StrUtil.isNotBlank(query.getEmpId()), "r.emp_id", query.getEmpId());
//根据充值类型查询
queryWrapper.eq(query.getRechargeType() != null, "r.recharge_type", query.getRechargeType());
//根据开始时间查询
queryWrapper.ge(query.getStartTime() != null, "r.create_time", query.getStartTime());
//根据结束时间查询
queryWrapper.le(query.getEndTime() != null, "r.create_time", query.getEndTime());
return queryWrapper;
}
}

View File

@@ -0,0 +1,76 @@
package top.ysoft.admin.consume.service.impl;
import cn.crane4j.annotation.AutoOperate;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.file.excel.util.ExcelUtils;
import top.ysoft.admin.consume.mapper.RecordMapper;
import top.ysoft.admin.consume.model.entity.RecordDO;
import top.ysoft.admin.consume.model.query.RecordQuery;
import top.ysoft.admin.consume.model.req.RecordReq;
import top.ysoft.admin.consume.model.resp.RecordResp;
import top.ysoft.admin.consume.service.RecordService;
import top.ysoft.admin.peopleBranch.mapper.PeopleMapper;
import java.util.List;
/**
* 消费记录业务实现
*
* @author zc
* @since 2026/01/21 17:53
*/
@Service
@RequiredArgsConstructor
public class RecordServiceImpl extends BaseServiceImpl<RecordMapper, RecordDO, RecordResp, RecordResp, RecordQuery, RecordReq> implements RecordService {
@Override
public PageResp<RecordResp> page(RecordQuery query, PageQuery pageQuery) {
QueryWrapper<RecordDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(StrUtil.isNotBlank(query.getEmpId()), "r.emp_id", query.getEmpId());
queryWrapper.eq(StrUtil.isNotBlank(query.getOutTradeId()), "r.out_trade_id", query.getOutTradeId());
queryWrapper.eq(null != query.getPayMode(), "r.pay_mode", query.getPayMode());
queryWrapper.eq(null != query.getResultCode(), "r.result_code", query.getResultCode());
queryWrapper.ge(null != query.getStartDate(), "r.start_date", query.getStartDate());
queryWrapper.le(null != query.getEndDate(), "r.end_date", query.getEndDate());
queryWrapper.le(null != query.getDevId(), "r.dev_id", query.getDevId());
queryWrapper.like(StrUtil.isNotBlank(query.getAccountName()), "p.name", query.getAccountName());
this.sort(queryWrapper, pageQuery);
IPage<RecordResp> page = baseMapper.selectRecordsPage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}
@Override
public void export(RecordQuery query, SortQuery sortQuery, HttpServletResponse response) {
QueryWrapper<RecordDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(StrUtil.isNotBlank(query.getEmpId()), "r.emp_id", query.getEmpId());
queryWrapper.eq(StrUtil.isNotBlank(query.getOutTradeId()), "r.out_trade_id", query.getOutTradeId());
queryWrapper.eq(null != query.getPayMode(), "r.pay_mode", query.getPayMode());
queryWrapper.eq(null != query.getResultCode(), "r.result_code", query.getResultCode());
queryWrapper.ge(null != query.getStartDate(), "r.start_date", query.getStartDate());
queryWrapper.le(null != query.getEndDate(), "r.end_date", query.getEndDate());
queryWrapper.le(null != query.getDevId(), "r.dev_id", query.getDevId());
queryWrapper.like(StrUtil.isNotBlank(query.getAccountName()), "p.name", query.getAccountName());
this.sort(queryWrapper, sortQuery);
List<RecordResp> list = baseMapper.selectRecordsExport(queryWrapper);
ExcelUtils.export(list, "消费记录_" + DateUtil.today(), this.getDetailClass(), response);
}
}

View File

@@ -0,0 +1,129 @@
package top.ysoft.admin.consume.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.file.excel.util.ExcelUtils;
import top.ysoft.admin.consume.mapper.ReportMapper;
import top.ysoft.admin.consume.model.query.ReportQuery;
import top.ysoft.admin.consume.model.resp.report.*;
import top.ysoft.admin.consume.service.ReportService;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ReportServiceImpl implements ReportService {
private final ReportMapper reportMapper;
/**
* 查询商户报表
*
* @param query 查询参数
* @return 商户报表列表
*/
@Override
public List<MerchantReport> queryMerchantReport(ReportQuery query) {
List<MerchantReport> list = reportMapper.queryMerchantReport(query);
BaseReport totalReport = reportMapper.totalReport(query);
MerchantReport merchantReport = new MerchantReport();
merchantReport.setMerchant("总价");
BeanUtil.copyProperties(totalReport, merchantReport);
list.add(merchantReport);
return list;
}
/**
* 查询餐段报表
*
* @param query 查询参数
* @return 餐段报表列表
*/
@Override
public List<MealReport> queryMealReport(ReportQuery query) {
List<MealReport> list = reportMapper.queryMealReport(query);
BaseReport totalReport = reportMapper.totalReport(query);
MealReport mealReport = new MealReport();
mealReport.setMerchant("总价");
BeanUtil.copyProperties(totalReport, mealReport);
list.add(mealReport);
return list;
}
/**
* 查询部门报表
*
* @param query 查询参数
* @return 部门报表列表
*/
@Override
public List<BranchReport> queryBranchReport(ReportQuery query) {
List<BranchReport> list = reportMapper.queryBranchReport(query);
BaseReport totalReport = reportMapper.totalReport(query);
BranchReport branchReport = new BranchReport();
branchReport.setBranchName("总价");
BeanUtil.copyProperties(totalReport, branchReport);
list.add(branchReport);
return list;
}
/**
* 查询人员报表
*
* @param query 查询参数
* @param pageQuery 分页查询参数
* @return 人员报表分页列表
*/
@Override
public PageResp<PeopleReport> page(ReportQuery query, PageQuery pageQuery) {
int offset = (pageQuery.getPage() - 1) * pageQuery.getSize();
List<PeopleReport> list = reportMapper.queryPeopleReport(query, pageQuery.getSize(), offset);
long total = reportMapper.selectCount(query);
return new PageResp<>(list, total);
}
@Override
public void exportMerchant(ReportQuery query, HttpServletResponse response) {
List<MerchantReport> list = reportMapper.queryMerchantReport(query);
BaseReport totalReport = reportMapper.totalReport(query);
MerchantReport merchantReport = new MerchantReport();
merchantReport.setMerchant("总价");
BeanUtil.copyProperties(totalReport, merchantReport);
list.add(merchantReport);
ExcelUtils.export(list, "商户营业报表_" + DateUtil.today(), MerchantReport.class, response);
}
@Override
public void exportMeal(ReportQuery query, HttpServletResponse response) {
List<MealReport> list = reportMapper.queryMealReport(query);
BaseReport totalReport = reportMapper.totalReport(query);
MealReport mealReport = new MealReport();
mealReport.setMerchant("总价");
BeanUtil.copyProperties(totalReport, mealReport);
list.add(mealReport);
ExcelUtils.export(list, "餐段消费报表_" + DateUtil.today(), MealReport.class, response);
}
@Override
public void exportBranch(ReportQuery query, HttpServletResponse response) {
List<BranchReport> list = reportMapper.queryBranchReport(query);
BaseReport totalReport = reportMapper.totalReport(query);
BranchReport branchReport = new BranchReport();
branchReport.setBranchName("总价");
BeanUtil.copyProperties(totalReport, branchReport);
list.add(branchReport);
ExcelUtils.export(list, "部门消费报表_" + DateUtil.today(), BranchReport.class, response);
}
@Override
public void exportPeople(ReportQuery query, HttpServletResponse response) {
List<PeopleReport> list = reportMapper.queryPeopleExport(query);
ExcelUtils.export(list, "个人消费报表_" + DateUtil.today(), PeopleReport.class, response);
}
}

View File

@@ -0,0 +1,25 @@
package top.ysoft.admin.consume.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.ysoft.admin.consume.mapper.TenantMapper;
import top.ysoft.admin.consume.model.entity.TenantDO;
import top.ysoft.admin.consume.model.query.TenantQuery;
import top.ysoft.admin.consume.model.req.TenantReq;
import top.ysoft.admin.consume.model.resp.TenantResp;
import top.ysoft.admin.consume.service.TenantService;
/**
* 商户信息业务实现
*
* @author zc
* @since 2026/01/30 14:55
*/
@Service
@RequiredArgsConstructor
public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, TenantResp, TenantResp, TenantQuery, TenantReq> implements TenantService {
}

View File

@@ -0,0 +1,25 @@
package top.ysoft.admin.consume.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.ysoft.admin.consume.mapper.TimeIntervalMapper;
import top.ysoft.admin.consume.model.entity.TimeIntervalDO;
import top.ysoft.admin.consume.model.query.TimeIntervalQuery;
import top.ysoft.admin.consume.model.req.TimeIntervalReq;
import top.ysoft.admin.consume.model.resp.TimeIntervalResp;
import top.ysoft.admin.consume.service.TimeIntervalService;
/**
* 消费时段业务实现
*
* @author zc
* @since 2026/01/30 14:56
*/
@Service
@RequiredArgsConstructor
public class TimeIntervalServiceImpl extends BaseServiceImpl<TimeIntervalMapper, TimeIntervalDO, TimeIntervalResp, TimeIntervalResp, TimeIntervalQuery, TimeIntervalReq> implements TimeIntervalService {
}

View File

@@ -0,0 +1,16 @@
package top.ysoft.admin.equipment.mapper;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.equipment.model.entity.EquipmentDO;
/**
* 设备信息 Mapper
*
* @author zc
* @since 2025/03/26 17:14
*/
@Repository
public interface EquipmentMapper extends BaseMapper<EquipmentDO> {
}

View File

@@ -0,0 +1,12 @@
package top.ysoft.admin.equipment.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.ysoft.admin.equipment.model.entity.ProductDO;
/**
* 产品信息 Mapper
*
* @author zc
* @since 2025/05/26 15:18
*/
public interface ProductMapper extends BaseMapper<ProductDO> {}

View File

@@ -0,0 +1,24 @@
package top.ysoft.admin.equipment.mapstruct;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.ysoft.admin.equipment.model.entity.EquipmentDO;
import top.ysoft.admin.equipment.model.req.EquipmentReq;
import top.ysoft.admin.equipment.model.resp.EquipmentResp;
import java.util.List;
@Mapper(componentModel = "spring")
public interface EquipmentConvert {
EquipmentResp DOToResp(EquipmentDO equipmentDO);
EquipmentReq RespToReq(EquipmentResp equipmentDO);
@Mapping(source = "id", target = "value")
@Mapping(source = "name", target = "label")
LabelValueResp labelValue(EquipmentDO equipmentDOS);
List<LabelValueResp> labelValueList(List<EquipmentDO> equipmentDOS);
}

View File

@@ -0,0 +1,20 @@
package top.ysoft.admin.equipment.mapstruct;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.ysoft.admin.equipment.model.entity.ProductDO;
import java.util.List;
@Mapper(componentModel = "spring")
public interface ProductConvert {
@Mapping(source = "id", target = "value")
@Mapping(source = "name", target = "label")
@Mapping(source = "avatar", target = "extra")
LabelValueResp labelValue(ProductDO productDO);
List<LabelValueResp> labelValueList(List<ProductDO> productDOS);
}

View File

@@ -0,0 +1,91 @@
package top.ysoft.admin.equipment.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
/**
* 设备信息实体
*
* @author zc
* @since 2025/03/26 17:10
*/
@Data
@TableName("sys_equipment")
public class EquipmentDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所属产品Id
*/
private Long productId;
/**
* 设备名称
*/
private String name;
/**
* 设备序列号
*/
private String sequence;
/**
* 设备Ip
*/
private String ip;
/**
* 设备密码
*/
private String password;
/**
* 设备区域
*/
private Long spaceId;
/**
* 设备位置
*/
private Long pointId;
/**
* 备注
*/
private String remark;
/**
* 对接状态(0未对接 1对接成功)
*/
private Integer state;
/**
* 设备状态(0在线 1离线)
*/
private String flag;
/**
* 设备回调地址
*/
private String backUrl;
/**
* 是否采集设备
*/
private String isCj;
/**
* 门禁位置 1.进2.出
*/
private String entryExitType;
/**
* 文件类型 1.图片2.视频
*/
private String fileType;
}

View File

@@ -0,0 +1,78 @@
package top.ysoft.admin.equipment.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.ysoft.admin.common.model.entity.BaseDO;
import java.io.Serial;
/**
* 产品信息实体
*
* @author zc
* @since 2025/05/26 15:18
*/
@Data
@TableName("sys_product")
public class ProductDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 产品图片地址
*/
private String avatar;
/**
* 产品名称
*/
private String name;
/**
* 产品型号
*/
private String version;
/**
* 产品品类(大类)
*/
private String bigtype;
/**
* 产品品类(小类)
*/
private String subtype;
/**
* 节点类型
*/
private String genre;
/**
* 产品描述
*/
private String describes;
/**
* 联网方式
*/
private String network;
/**
* 数据格式
*/
private String dataFormat;
/**
* 数据校验级别
*/
private String scale;
/**
* 认证方式
*/
private String attestation;
}

View File

@@ -0,0 +1,53 @@
package top.ysoft.admin.equipment.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
/**
* 设备信息查询条件
*
* @author zc
* @since 2025/03/26 17:14
*/
@Data
@Schema(description = "设备信息查询条件")
public class EquipmentQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备名称
*/
@Schema(description = "设备名称")
@Query(type = QueryType.LIKE)
private String name;
/**
* 设备序列号
*/
@Schema(description = "设备序列号")
@Query(type = QueryType.EQ)
private String sequence;
/**
* 所属产品
*/
@Schema(description = "所属产品")
@Query(type = QueryType.EQ)
private Long productId;
/**
* 设备状态(0在线 1离线)
*/
@Schema(description = "设备状态(0在线 1离线)")
@Query(type = QueryType.EQ)
private String flag;
}

View File

@@ -0,0 +1,47 @@
package top.ysoft.admin.equipment.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 产品信息查询条件
*
* @author zc
* @since 2025/05/26 15:18
*/
@Data
@Schema(description = "产品信息查询条件")
public class ProductQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 产品名称
*/
@Schema(description = "产品名称")
@Query(type = QueryType.LIKE)
private String name;
/**
* 产品名称
*/
@Schema(description = "产品型号")
@Query(type = QueryType.LIKE)
private String version;
/**
* 产品名称
*/
@Schema(description = "节点类型")
@Query(type = QueryType.EQ)
private String genre;
}

View File

@@ -0,0 +1,142 @@
package top.ysoft.admin.equipment.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 创建或修改设备信息参数
*
* @author zc
* @since 2025/03/26 16:40
*/
@Data
@Schema(description = "创建或修改设备信息参数")
public class EquipmentReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备Id
*/
@Schema(description = "设备Id")
private Long id;
/**
* 所属产品Id
*/
@Schema(description = "所属产品Id")
private Long productId;
/**
* 设备名称
*/
@Schema(description = "设备名称")
@NotBlank(message = "设备名称不能为空")
@Length(max = 50, message = "设备名称长度不能超过 {max} 个字符")
private String name;
/**
* 设备序列号
*/
@Schema(description = "设备序列号")
@Length(max = 50, message = "设备序列号长度不能超过 {max} 个字符")
private String sequence;
/**
* 设备Ip
*/
@Schema(description = "设备Ip")
@Length(max = 50, message = "设备Ip长度不能超过 {max} 个字符")
private String ip;
/**
* 设备密码
*/
@Schema(description = "设备密码")
@Length(max = 50, message = "设备密码长度不能超过 {max} 个字符")
private String password;
/**
* 设备区域
*/
@Schema(description = "设备区域")
private Long spaceId;
/**
* 设备位置
*/
@Schema(description = "设备位置")
private Long pointId;
/**
* 备注
*/
@Schema(description = "备注")
@Length(max = 200, message = "备注长度不能超过 {max} 个字符")
private String remark;
/**
*
*/
@Schema(description = "")
private Long createUser;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
*
*/
@Schema(description = "")
private Long updateUser;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
/**
* 对接状态(0未对接 1对接成功)
*/
@Schema(description = "对接状态(0未对接 1对接成功)")
private Integer state;
/**
* 设备状态(0在线 1离线)
*/
@Schema(description = "设备状态(0在线 1离线)")
@Length(max = 50, message = "设备状态(0在线 1离线)长度不能超过 {max} 个字符")
private String flag;
/*** 设备回调地址 ***/
private String backUrl;
/**
* 是否采集设备
*/
@Schema(description = "是否采集设备")
private String isCj;
/**
* 门卡号码
*/
private String doorNo;
/**
* 门禁位置 1.进2.出
*/
@Schema(description = "门禁位置 1.进2.出")
private String entryExitType;
}

View File

@@ -0,0 +1,105 @@
package top.ysoft.admin.equipment.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 创建或修改产品信息参数
*
* @author zc
* @since 2025/05/26 15:18
*/
@Data
@Schema(description = "创建或修改产品信息参数")
public class ProductReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 产品图片地址
*/
@Schema(description = "产品图片地址")
@Length(max = 500, message = "产品图片地址长度不能超过 {max} 个字符")
private String avatar;
/**
* 产品名称
*/
@Schema(description = "产品名称")
@NotBlank(message = "产品名称不能为空")
@Length(max = 50, message = "产品名称长度不能超过 {max} 个字符")
private String name;
/**
* 产品型号
*/
@Schema(description = "产品型号")
@Length(max = 500, message = "产品型号长度不能超过 {max} 个字符")
private String version;
/**
* 产品品类(大类)
*/
@Schema(description = "产品品类(大类)")
@Length(max = 1, message = "产品品类(大类)长度不能超过 {max} 个字符")
private String bigtype;
/**
* 产品品类(小类)
*/
@Schema(description = "产品品类(小类)")
@Length(max = 1, message = "产品品类(小类)长度不能超过 {max} 个字符")
private String subtype;
/**
* 节点类型
*/
@Schema(description = "节点类型")
@Length(max = 1, message = "节点类型长度不能超过 {max} 个字符")
private String genre;
/**
* 产品描述
*/
@Schema(description = "产品描述")
@Length(max = 200, message = "产品描述长度不能超过 {max} 个字符")
private String describes;
/**
* 联网方式
*/
@Schema(description = "联网方式")
@Length(max = 1, message = "联网方式长度不能超过 {max} 个字符")
private String network;
/**
* 数据格式
*/
@Schema(description = "数据格式")
@Length(max = 50, message = "数据格式长度不能超过 {max} 个字符")
private String dataFormat;
/**
* 数据校验级别
*/
@Schema(description = "数据校验级别")
@Length(max = 50, message = "数据校验级别长度不能超过 {max} 个字符")
private String scale;
/**
* 认证方式
*/
@Schema(description = "认证方式")
@Length(max = 1, message = "认证方式长度不能超过 {max} 个字符")
private String attestation;
}

View File

@@ -0,0 +1,125 @@
package top.ysoft.admin.equipment.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
/**
* 设备信息详情信息
*
* @author zc
* @since 2025/03/26 17:11
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "设备信息详情信息")
public class EquipmentDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所属产品Id
*/
@Schema(description = "所属产品Id")
@ExcelProperty(value = "所属产品Id")
private Long productId;
/**
* 设备名称
*/
@Schema(description = "设备名称")
@ExcelProperty(value = "设备名称")
private String name;
/**
* 设备序列号
*/
@Schema(description = "设备序列号")
@ExcelProperty(value = "设备序列号")
private String sequence;
/**
* 设备Ip
*/
@Schema(description = "设备Ip")
@ExcelProperty(value = "设备Ip")
private String ip;
/**
* 设备密码
*/
@Schema(description = "设备密码")
@ExcelProperty(value = "设备密码")
private String password;
/**
* 设备区域
*/
@Schema(description = "设备区域")
@ExcelProperty(value = "设备区域")
private Long spaceId;
/**
* 设备位置
*/
@Schema(description = "设备位置")
@ExcelProperty(value = "设备位置")
private Long pointId;
/**
* 备注
*/
@Schema(description = "备注")
@ExcelProperty(value = "备注")
private String remark;
/**
* 对接状态(0未对接 1对接成功)
*/
@Schema(description = "对接状态(0未对接 1对接成功)")
@ExcelProperty(value = "对接状态(0未对接 1对接成功)")
private Integer state;
/**
* 设备状态(0在线 1离线)
*/
@Schema(description = "设备状态(0在线 1离线)")
@ExcelProperty(value = "设备状态(0在线 1离线)")
private String flag;
/**
* 设备回调地址
*/
@Schema(description = "设备回调地址")
@ExcelProperty(value = "设备回调地址")
private String backUrl;
/**
* 是否采集设备
*/
@Schema(description = "是否采集设备")
@ExcelProperty(value = "是否采集设备")
private String isCj;
/**
* 门禁位置 1.进2.出
*/
@Schema(description = "门禁位置 1.进2.出")
@ExcelProperty(value = "门禁位置 1.进2.出")
private String entryExitType;
/**
* 文件类型 1.图片2.视频
*/
@Schema(description = "文件类型 1.图片2.视频")
@ExcelProperty(value = "文件类型 1.图片2.视频")
private String fileType;
}

View File

@@ -0,0 +1,141 @@
package top.ysoft.admin.equipment.model.resp;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.Mapping;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.ysoft.admin.common.constant.ContainerConstants;
import top.ysoft.admin.common.model.resp.BaseResp;
import java.io.Serial;
import java.time.*;
/**
* 设备信息信息
*
* @author zc
* @since 2025/03/26 17:11
*/
@Data
@Schema(description = "设备信息信息")
public class EquipmentResp extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所属产品Id
*/
@Schema(description = "所属产品Id")
@Assemble(props = @Mapping(src = "name", ref = "productName"), container = ContainerConstants.PRODUCT_NAME_AVATAR)
@Assemble(props = @Mapping(src = "avatar", ref = "avatar"), container = ContainerConstants.PRODUCT_NAME_AVATAR)
private Long productId;
/**
* 产品名称
*/
@Schema(description = "产品名称")
private String productName;
/**
* 产品图片
*/
@Schema(description = "产品图片")
private String avatar;
/**
* 设备名称
*/
@Schema(description = "设备名称")
private String name;
/**
* 设备序列号
*/
@Schema(description = "设备序列号")
private String sequence;
/**
* 设备Ip
*/
@Schema(description = "设备Ip")
private String ip;
/**
* 设备密码
*/
@Schema(description = "设备密码")
private String password;
/**
* 设备区域
*/
@Schema(description = "设备区域")
private Long spaceId;
/**
* 设备位置
*/
@Schema(description = "设备位置")
private Long pointId;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
/**
*
*/
@Schema(description = "")
private Long updateUser;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
/**
* 对接状态(0未对接 1对接成功)
*/
@Schema(description = "对接状态(0未对接 1对接成功)")
private Integer state;
/**
* 设备状态(0在线 1离线)
*/
@Schema(description = "设备状态(0在线 1离线)")
private String flag;
/**
* 设备回调地址
*/
@Schema(description = "设备回调地址")
private String backUrl;
/**
* 是否采集设备
*/
@Schema(description = "是否采集设备")
private String isCj;
/**
* 门禁位置 1.进2.出
*/
@Schema(description = "门禁位置 1.进2.出")
private String entryExitType;
/**
* 文件类型 1.图片2.视频
*/
@Schema(description = "文件类型 1.图片2.视频")
private String fileType;
/** 门卡号码 */
@Schema(name = "卡号")
private String doorNo;
}

View File

@@ -0,0 +1,105 @@
package top.ysoft.admin.equipment.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 产品信息详情信息
*
* @author zc
* @since 2025/05/26 15:18
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "产品信息详情信息")
public class ProductDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 产品图片地址
*/
@Schema(description = "产品图片地址")
@ExcelProperty(value = "产品图片地址")
private String avatar;
/**
* 产品名称
*/
@Schema(description = "产品名称")
@ExcelProperty(value = "产品名称")
private String name;
/**
* 产品型号
*/
@Schema(description = "产品型号")
@ExcelProperty(value = "产品型号")
private String version;
/**
* 产品品类(大类)
*/
@Schema(description = "产品品类(大类)")
@ExcelProperty(value = "产品品类(大类)")
private String bigtype;
/**
* 产品品类(小类)
*/
@Schema(description = "产品品类(小类)")
@ExcelProperty(value = "产品品类(小类)")
private String subtype;
/**
* 节点类型
*/
@Schema(description = "节点类型")
@ExcelProperty(value = "节点类型")
private String genre;
/**
* 产品描述
*/
@Schema(description = "产品描述")
@ExcelProperty(value = "产品描述")
private String describes;
/**
* 联网方式
*/
@Schema(description = "联网方式")
@ExcelProperty(value = "联网方式")
private String network;
/**
* 数据格式
*/
@Schema(description = "数据格式")
@ExcelProperty(value = "数据格式")
private String dataFormat;
/**
* 数据校验级别
*/
@Schema(description = "数据校验级别")
@ExcelProperty(value = "数据校验级别")
private String scale;
/**
* 认证方式
*/
@Schema(description = "认证方式")
@ExcelProperty(value = "认证方式")
private String attestation;
}

View File

@@ -0,0 +1,102 @@
package top.ysoft.admin.equipment.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.ysoft.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 产品信息信息
*
* @author zc
* @since 2025/05/26 15:18
*/
@Data
@Schema(description = "产品信息信息")
public class ProductResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 产品图片地址
*/
@Schema(description = "产品图片地址")
private String avatar;
/**
* 产品名称
*/
@Schema(description = "产品名称")
private String name;
/**
* 产品型号
*/
@Schema(description = "产品型号")
private String version;
/**
* 产品品类(大类)
*/
@Schema(description = "产品品类(大类)")
private String bigtype;
/**
* 产品品类(小类)
*/
@Schema(description = "产品品类(小类)")
private String subtype;
/**
* 节点类型
*/
@Schema(description = "节点类型")
private String genre;
/**
* 产品描述
*/
@Schema(description = "产品描述")
private String describes;
/**
* 联网方式
*/
@Schema(description = "联网方式")
private String network;
/**
* 数据格式
*/
@Schema(description = "数据格式")
private String dataFormat;
/**
* 数据校验级别
*/
@Schema(description = "数据校验级别")
private String scale;
/**
* 认证方式
*/
@Schema(description = "认证方式")
private String attestation;
/**
* 更新者
*/
@Schema(description = "更新者")
private Long updateUser;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,27 @@
package top.ysoft.admin.equipment.service;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.equipment.model.query.EquipmentQuery;
import top.ysoft.admin.equipment.model.req.EquipmentReq;
import top.ysoft.admin.equipment.model.resp.EquipmentDetailResp;
import top.ysoft.admin.equipment.model.resp.EquipmentResp;
import java.util.List;
/**
* 设备信息业务接口
*
* @author zc
* @since 2025/03/26 17:14
*/
public interface EquipmentService extends BaseService<EquipmentResp, EquipmentDetailResp, EquipmentQuery, EquipmentReq> {
EquipmentResp selectSysEquipment(EquipmentReq equipment);
List<LabelValueResp> getEquipmentNameList(EquipmentReq equipmentReq);
EquipmentResp selectEquipmentById(Long id);
String getEquipmentName(Long id);
}

View File

@@ -0,0 +1,23 @@
package top.ysoft.admin.equipment.service;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.ysoft.admin.equipment.model.entity.ProductDO;
import top.ysoft.admin.equipment.model.query.ProductQuery;
import top.ysoft.admin.equipment.model.req.ProductReq;
import top.ysoft.admin.equipment.model.resp.ProductResp;
import java.util.List;
/**
* 产品信息业务接口
*
* @author zc
* @since 2025/05/26 15:18
*/
public interface ProductService extends BaseService<ProductResp, ProductResp, ProductQuery, ProductReq> {
List<ProductDO> getProductNameAvatarList();
List<LabelValueResp> getProductNameList();
}

Some files were not shown because too many files have changed in this diff Show More