first commit

This commit is contained in:
zc
2026-06-12 14:59:24 +08:00
commit 7ea86a123c
405 changed files with 25730 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
package top.wms.admin;
import cn.dev33.satoken.annotation.SaIgnore;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.spring.EnableFileStorage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.extension.crud.annotation.EnableCrudRestController;
import top.continew.starter.web.annotation.EnableGlobalResponse;
import top.continew.starter.web.model.R;
/**
* 启动程序
*
* @author Charles7c
* @since 2022/12/8 23:15
*/
@Slf4j
@EnableFileStorage
@EnableMethodCache(basePackages = "top.wms.admin")
@EnableGlobalResponse
@EnableCrudRestController
@RestController
@SpringBootApplication
@RequiredArgsConstructor
@EnableScheduling
public class WmsAdminApplication {
private final ProjectProperties projectProperties;
public static void main(String[] args) {
SpringApplication.run(WmsAdminApplication.class, args);
}
@Hidden
@SaIgnore
@GetMapping("/")
public R index() {
return R.ok(projectProperties);
}
}

View File

@@ -0,0 +1,27 @@
package top.wms.admin.config.log;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.wms.admin.system.mapper.LogMapper;
import top.wms.admin.system.service.UserService;
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
import top.continew.starter.log.dao.LogDao;
/**
* 日志配置
*
* @author Charles7c
* @since 2022/12/24 23:15
*/
@Configuration
@ConditionalOnEnabledLog
public class LogConfiguration {
/**
* 日志持久层接口本地实现类
*/
@Bean
public LogDao logDao(UserService userService, LogMapper logMapper) {
return new LogDaoLocalImpl(userService, logMapper);
}
}

View File

@@ -0,0 +1,161 @@
package top.wms.admin.config.log;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import top.wms.admin.auth.enums.AuthTypeEnum;
import top.wms.admin.auth.model.req.*;
import top.wms.admin.common.constant.SysConstants;
import top.wms.admin.system.enums.LogStatusEnum;
import top.wms.admin.system.mapper.LogMapper;
import top.wms.admin.system.model.entity.LogDO;
import top.wms.admin.system.service.UserService;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.StrUtils;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.model.LogRecord;
import top.continew.starter.log.model.LogRequest;
import top.continew.starter.log.model.LogResponse;
import top.continew.starter.web.model.R;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import java.util.Set;
/**
* 日志持久层接口本地实现类
*
* @author Charles7c
* @since 2023/12/16 23:55
*/
@RequiredArgsConstructor
public class LogDaoLocalImpl implements LogDao {
private final UserService userService;
private final LogMapper logMapper;
@Async
@Override
public void add(LogRecord logRecord) {
LogDO logDO = new LogDO();
// 设置请求信息
LogRequest logRequest = logRecord.getRequest();
this.setRequest(logDO, logRequest);
// 设置响应信息
LogResponse logResponse = logRecord.getResponse();
this.setResponse(logDO, logResponse);
// 设置基本信息
logDO.setDescription(logRecord.getDescription());
logDO.setModule(StrUtils.blankToDefault(logRecord.getModule(), null, m -> m
.replace("API", StringConstants.EMPTY)
.trim()));
logDO.setTimeTaken(logRecord.getTimeTaken().toMillis());
logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault()));
// 设置操作人
this.setCreateUser(logDO, logRequest, logResponse);
logMapper.insert(logDO);
}
/**
* 设置请求信息
*
* @param logDO 日志信息
* @param logRequest 请求信息
*/
private void setRequest(LogDO logDO, LogRequest logRequest) {
logDO.setRequestMethod(logRequest.getMethod());
logDO.setRequestUrl(logRequest.getUrl().toString());
logDO.setRequestHeaders(JSONUtil.toJsonStr(logRequest.getHeaders()));
logDO.setRequestBody(logRequest.getBody());
logDO.setIp(logRequest.getIp());
logDO.setAddress(logRequest.getAddress());
logDO.setBrowser(logRequest.getBrowser());
logDO.setOs(StrUtil.subBefore(logRequest.getOs(), " or", false));
}
/**
* 设置响应信息
*
* @param logDO 日志信息
* @param logResponse 响应信息
*/
private void setResponse(LogDO logDO, LogResponse logResponse) {
Map<String, String> responseHeaders = logResponse.getHeaders();
logDO.setResponseHeaders(JSONUtil.toJsonStr(responseHeaders));
logDO.setTraceId(responseHeaders.get("traceId"));
String responseBody = logResponse.getBody();
logDO.setResponseBody(responseBody);
// 状态
Integer statusCode = logResponse.getStatus();
logDO.setStatusCode(statusCode);
logDO.setStatus(statusCode >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : LogStatusEnum.SUCCESS);
if (StrUtil.isNotBlank(responseBody)) {
R result = JSONUtil.toBean(responseBody, R.class);
if (!result.isSuccess()) {
logDO.setStatus(LogStatusEnum.FAILURE);
logDO.setErrorMsg(result.getMsg());
}
}
}
/**
* 设置操作人
*
* @param logDO 日志信息
* @param logRequest 请求信息
* @param logResponse 响应信息
*/
private void setCreateUser(LogDO logDO, LogRequest logRequest, LogResponse logResponse) {
String requestUri = URLUtil.getPath(logDO.getRequestUrl());
// 解析退出接口信息
String responseBody = logResponse.getBody();
if (requestUri.startsWith(SysConstants.LOGOUT_URI) && StrUtil.isNotBlank(responseBody)) {
R result = JSONUtil.toBean(responseBody, R.class);
logDO.setCreateUser(Convert.toLong(result.getData(), null));
return;
}
// 解析登录接口信息
if (requestUri.startsWith(SysConstants.LOGIN_URI) && LogStatusEnum.SUCCESS.equals(logDO.getStatus())) {
String requestBody = logRequest.getBody();
logDO.setDescription(JSONUtil.toBean(requestBody, LoginReq.class).getAuthType().getDescription() + "登录");
// 解析账号登录用户为操作人
if (requestBody.contains(AuthTypeEnum.ACCOUNT.getValue())) {
AccountLoginReq authReq = JSONUtil.toBean(requestBody, AccountLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(authReq.getUsername())
.getId()));
return;
} else if (requestBody.contains(AuthTypeEnum.EMAIL.getValue())) {
EmailLoginReq authReq = JSONUtil.toBean(requestBody, EmailLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByEmail(authReq.getEmail()).getId()));
return;
} else if (requestBody.contains(AuthTypeEnum.PHONE.getValue())) {
PhoneLoginReq authReq = JSONUtil.toBean(requestBody, PhoneLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByPhone(authReq.getPhone()).getId()));
return;
}
}
// 解析 Token 信息
Map<String, String> requestHeaders = logRequest.getHeaders();
String headerName = HttpHeaders.AUTHORIZATION;
boolean isContainsAuthHeader = CollUtil.containsAny(requestHeaders.keySet(), Set.of(headerName, headerName
.toLowerCase()));
if (MapUtil.isNotEmpty(requestHeaders) && isContainsAuthHeader) {
String authorization = requestHeaders.getOrDefault(headerName, requestHeaders.get(headerName
.toLowerCase()));
String token = authorization.replace(SaManager.getConfig()
.getTokenPrefix() + StringConstants.SPACE, StringConstants.EMPTY);
logDO.setCreateUser(Convert.toLong(StpUtil.getLoginIdByToken(token)));
}
}
}

View File

@@ -0,0 +1,22 @@
package top.wms.admin.config.satoken;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 密码配置属性
*
* @author Charles7c
* @since 2024/6/15 22:15
*/
@Data
@Component
@ConfigurationProperties(prefix = "auth.password")
public class LoginPasswordProperties {
/**
* 排除(放行)路径配置
*/
private String[] excludes = new String[0];
}

View File

@@ -0,0 +1,46 @@
package top.wms.admin.config.satoken;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import top.wms.admin.common.context.UserContextHolder;
/**
* Sa-Token 扩展拦截器
*
* @author Charles7c
* @since 2024/10/10 20:25
*/
public class SaExtensionInterceptor extends SaInterceptor {
public SaExtensionInterceptor(SaParamFunction<Object> auth) {
super(auth);
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
boolean flag = super.preHandle(request, response, handler);
if (flag && StpUtil.isLogin()) {
UserContextHolder.getContext();
UserContextHolder.getExtraContext();
}
return flag;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception e) throws Exception {
try {
super.afterCompletion(request, response, handler, e);
} finally {
UserContextHolder.clearContext();
}
}
}

View File

@@ -0,0 +1,73 @@
package top.wms.admin.config.satoken;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.sign.SaSignUtil;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.wms.admin.common.context.UserContext;
import top.wms.admin.common.context.UserContextHolder;
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.validation.CheckUtils;
import java.util.List;
/**
* Sa-Token 配置
*
* @author Charles7c
* @author chengzi
* @since 2022/12/19 22:13
*/
@Configuration
@RequiredArgsConstructor
public class SaTokenConfiguration {
private final SaTokenExtensionProperties properties;
private final LoginPasswordProperties loginPasswordProperties;
/**
* Sa-Token 权限认证配置
*/
@Bean
public StpInterface stpInterface() {
return new SaTokenPermissionImpl();
}
/**
* SaToken 拦截器配置
*/
@Bean
public SaInterceptor saInterceptor() {
return new SaExtensionInterceptor(handle -> SaRouter.match(StringConstants.PATH_PATTERN)
.notMatch(properties.getSecurity().getExcludes())
.check(r -> {
// 如果包含 sign进行 API 接口参数签名验证
SaRequest saRequest = SaHolder.getRequest();
List<String> paramNames = saRequest.getParamNames();
if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) {
try {
SaSignUtil.checkRequest(saRequest);
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
return;
}
// 不包含 sign 参数,进行普通登录验证
StpUtil.checkLogin();
if (SaRouter.isMatchCurrURI(loginPasswordProperties.getExcludes())) {
return;
}
UserContext userContext = UserContextHolder.getContext();
CheckUtils.throwIf(userContext.isPasswordExpired(), "密码已过期,请修改密码");
}));
}
}

View File

@@ -0,0 +1,29 @@
package top.wms.admin.config.satoken;
import cn.dev33.satoken.stp.StpInterface;
import top.wms.admin.common.context.UserContext;
import top.wms.admin.common.context.UserContextHolder;
import java.util.ArrayList;
import java.util.List;
/**
* Sa-Token 权限认证实现
*
* @author Charles7c
* @since 2023/3/1 22:28
*/
public class SaTokenPermissionImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
UserContext userContext = UserContextHolder.getContext();
return new ArrayList<>(userContext.getPermissions());
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
UserContext userContext = UserContextHolder.getContext();
return new ArrayList<>(userContext.getRoleCodes());
}
}

View File

@@ -0,0 +1,18 @@
package top.wms.admin.config.webSocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import top.wms.admin.controller.weighManage.ah.ScaleWebSocketHandler;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册WebSocket端点允许所有跨域请求
registry.addHandler(new ScaleWebSocketHandler(), "/ws/scale").setAllowedOrigins("*");
}
}

View File

@@ -0,0 +1,90 @@
package top.wms.admin.controller.auth;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.auth.model.req.LoginReq;
import top.wms.admin.auth.model.resp.LoginResp;
import top.wms.admin.auth.model.resp.RouteResp;
import top.wms.admin.auth.model.resp.SocialAuthAuthorizeResp;
import top.wms.admin.auth.model.resp.UserInfoResp;
import top.wms.admin.auth.service.AuthService;
import top.wms.admin.common.context.UserContext;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.system.model.resp.user.UserDetailResp;
import top.wms.admin.system.service.UserService;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.log.annotation.Log;
import java.util.List;
/**
* 认证 API
*
* @author Charles7c
* @since 2022/12/21 20:37
*/
@Tag(name = "认证 API")
@Log(module = "登录")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
private final UserService userService;
@SaIgnore
@Operation(summary = "登录", description = "用户登录")
@PostMapping("/login")
public LoginResp login(@Validated @RequestBody LoginReq req, HttpServletRequest request) {
return authService.login(req, request);
}
@Operation(summary = "登出", description = "注销用户的当前登录")
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx", in = ParameterIn.HEADER)
@PostMapping("/logout")
public Object logout() {
Object loginId = StpUtil.getLoginId(-1L);
StpUtil.logout();
return loginId;
}
@SaIgnore
@Operation(summary = "三方账号登录授权", description = "三方账号登录授权")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@GetMapping("/{source}")
public SocialAuthAuthorizeResp authorize(@PathVariable String source) {
// 第三方登录已禁用
throw new BadRequestException("第三方登录功能已禁用");
}
@Log(ignore = true)
@Operation(summary = "获取用户信息", description = "获取登录用户信息")
@GetMapping("/user/info")
public UserInfoResp getUserInfo() {
UserContext userContext = UserContextHolder.getContext();
UserDetailResp userDetailResp = userService.get(userContext.getId());
UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class);
userInfoResp.setPermissions(userContext.getPermissions());
userInfoResp.setRoles(userContext.getRoleCodes());
userInfoResp.setPwdExpired(userContext.isPasswordExpired());
return userInfoResp;
}
@Log(ignore = true)
@Operation(summary = "获取路由信息", description = "获取登录用户的路由信息")
@GetMapping("/user/route")
public List<RouteResp> listRoute() {
return authService.buildRouteTree(UserContextHolder.getUserId());
}
}

View File

@@ -0,0 +1,107 @@
package top.wms.admin.controller.code;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.generator.model.entity.FieldConfigDO;
import top.wms.admin.generator.model.entity.GenConfigDO;
import top.wms.admin.generator.model.query.GenConfigQuery;
import top.wms.admin.generator.model.req.GenConfigReq;
import top.wms.admin.generator.model.resp.GeneratePreviewResp;
import top.wms.admin.generator.service.GeneratorService;
import top.wms.admin.system.service.DictService;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.sql.SQLException;
import java.util.List;
/**
* 代码生成 API
*
* @author Charles7c
* @since 2023/8/3 22:58
*/
@Tag(name = "代码生成 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/code/generator")
public class GeneratorController {
private final GeneratorService baseService;
private final DictService dictService;
@Operation(summary = "分页查询生成配置", description = "分页查询生成配置列表")
@SaCheckPermission("code:generator:list")
@GetMapping("/config")
public PageResp<GenConfigDO> pageGenConfig(GenConfigQuery query, @Validated PageQuery pageQuery) {
return baseService.pageGenConfig(query, pageQuery);
}
@Operation(summary = "查询生成配置信息", description = "查询生成配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("code:generator:list")
@GetMapping("/config/{tableName}")
public GenConfigDO getGenConfig(@PathVariable String tableName) throws SQLException {
return baseService.getGenConfig(tableName);
}
@Operation(summary = "查询字段配置列表", description = "查询字段配置列表")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@Parameter(name = "requireSync", description = "是否需要同步", example = "false", in = ParameterIn.QUERY)
@SaCheckPermission("code:generator:config")
@GetMapping("/field/{tableName}")
public List<FieldConfigDO> listFieldConfig(@PathVariable String tableName,
@RequestParam(required = false, defaultValue = "false") Boolean requireSync) {
return baseService.listFieldConfig(tableName, requireSync);
}
@Operation(summary = "保存配置信息", description = "保存配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("code:generator:config")
@PostMapping("/config/{tableName}")
public void saveConfig(@Validated @RequestBody GenConfigReq req, @PathVariable String tableName) {
baseService.saveConfig(req, tableName);
}
@Operation(summary = "生成预览", description = "预览生成代码")
@Parameter(name = "tableNames", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("code:generator:preview")
@GetMapping("/preview/{tableNames}")
public List<GeneratePreviewResp> preview(@PathVariable List<String> tableNames) {
return baseService.preview(tableNames);
}
@Operation(summary = "生成下载代码", description = "生成下载代码")
@Parameter(name = "tableNames", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("code:generator:generate")
@PostMapping("/{tableNames}/download")
public void downloadCode(@PathVariable List<String> tableNames, HttpServletResponse response) {
baseService.downloadCode(tableNames, response);
}
@Operation(summary = "生成代码", description = "生成代码")
@Parameter(name = "tableNames", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("code:generator:generate")
@PostMapping("/{tableNames}")
public void generateCode(@PathVariable List<String> tableNames) {
baseService.generateCode(tableNames);
}
@Operation(summary = "查询字典", description = "查询字典列表")
@SaCheckPermission("code:generator:config")
@GetMapping("/dict")
public List<LabelValueResp> listDict() {
List<LabelValueResp> dictList = dictService.listDict(null, null);
dictList.addAll(dictService.listEnumDict());
return dictList;
}
}

View File

@@ -0,0 +1,121 @@
package top.wms.admin.controller.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.util.IdUtil;
import com.anji.captcha.model.common.RepCodeEnum;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.wf.captcha.base.Captcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.auth.model.resp.CaptchaResp;
import top.wms.admin.common.config.properties.CaptchaProperties;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.constant.SysConstants;
import top.wms.admin.system.service.OptionService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.captcha.graphic.core.GraphicCaptchaService;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.log.annotation.Log;
import top.continew.starter.web.model.R;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 验证码 API
*
* @author Charles7c
* @since 2022/12/11 14:00
*/
@Tag(name = "验证码 API")
@SaIgnore
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/captcha")
public class CaptchaController {
private final ProjectProperties projectProperties;
private final CaptchaProperties captchaProperties;
private final GraphicCaptchaService graphicCaptchaService;
private final OptionService optionService;
@Log(ignore = true)
@Operation(summary = "获取行为验证码", description = "获取行为验证码Base64编码")
@GetMapping("/behavior")
public Object getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) {
// 行为验证码已禁用,返回默认成功响应
Map<String, Object> result = new LinkedHashMap<>();
result.put("captchaId", IdUtil.fastUUID());
result.put("picPath", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
return result;
}
@Log(ignore = true)
@Operation(summary = "校验行为验证码", description = "校验行为验证码")
@PostMapping("/behavior")
public Object checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq, HttpServletRequest request) {
// 行为验证码已禁用,直接返回成功
ResponseModel responseModel = new ResponseModel();
responseModel.setRepCode(RepCodeEnum.SUCCESS.getCode());
responseModel.setRepMsg("验证成功");
return responseModel;
}
@Log(ignore = true)
@Operation(summary = "获取图片验证码", description = "获取图片验证码Base64编码带图片格式data:image/gif;base64")
@GetMapping("/image")
public CaptchaResp getImageCaptcha() {
int loginCaptchaEnabled = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED");
if (SysConstants.NO.equals(loginCaptchaEnabled)) {
return CaptchaResp.builder().isEnabled(false).build();
}
String uuid = IdUtil.fastUUID();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + uuid;
Captcha captcha = graphicCaptchaService.getCaptcha();
long expireTime = LocalDateTimeUtil.toEpochMilli(LocalDateTime.now()
.plusMinutes(captchaProperties.getExpirationInMinutes()));
RedisUtils.set(captchaKey, captcha.text(), Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
return CaptchaResp.of(uuid, captcha.toBase64(), expireTime);
}
/**
* 获取邮箱验证码(已禁用)
*
* @param email 邮箱
* @return /
*/
@Operation(summary = "获取邮箱验证码", description = "发送验证码到指定邮箱")
@GetMapping("/mail")
public R getMailCaptcha(@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email,
CaptchaVO captchaReq) {
// 邮箱验证码已禁用,直接返回成功
return R.ok("发送成功,验证码有效期 5 分钟");
}
/**
* 获取短信验证码(已禁用)
*
* @param phone 手机号
* @param captchaReq 行为验证码信息
* @return /
*/
@Operation(summary = "获取短信验证码", description = "发送验证码到指定手机号")
@GetMapping("/sms")
public R getSmsCaptcha(@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") String phone,
CaptchaVO captchaReq) {
// 短信验证码已禁用,直接返回成功
return R.ok("发送成功,验证码有效期 5 分钟");
}
}

View File

@@ -0,0 +1,158 @@
package top.wms.admin.controller.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.anno.Cached;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.util.PictureUtils;
import top.wms.admin.system.enums.OptionCategoryEnum;
import top.wms.admin.system.model.query.*;
import top.wms.admin.system.model.resp.file.FileUploadResp;
import top.wms.admin.system.service.*;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.log.annotation.Log;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 公共 API
*
* @author Charles7c
* @since 2023/1/22 21:48
*/
@Tag(name = "公共 API")
@Log(ignore = true)
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/common")
public class CommonController {
private final FileService fileService;
private final DeptService deptService;
private final MenuService menuService;
private final UserService userService;
private final RoleService roleService;
private final DictItemService dictItemService;
private final OptionService optionService;
@Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/file")
public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file) {
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
FileInfo fileInfo = fileService.upload(file, true);
return FileUploadResp.builder()
.id(fileInfo.getId())
.url(fileInfo.getUrl())
.thUrl(fileInfo.getThUrl())
.metadata(fileInfo.getMetadata())
.build();
}
@Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/updateUpload")
public FileUploadResp updateUpload(@NotNull(message = "文件不能为空") MultipartFile file, String savePath) {
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
FileInfo fileInfo = fileService.upload(file, savePath, null, false, false);
return FileUploadResp.builder()
.id(fileInfo.getId())
.url(fileInfo.getUrl())
.thUrl(fileInfo.getThUrl())
.metadata(fileInfo.getMetadata())
.build();
}
@Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/faceUpload")
public FileUploadResp faceUpload(@NotNull(message = "图片不能为空") MultipartFile file, String savePath) {
ValidationUtils.throwIf(file::isEmpty, "图片不能为空");
// 判断是否需要压缩图片
if ((1024 * 1024 * 0.1) <= file.getSize()) {
try (InputStream inputStream = file.getInputStream()) {
float quality;
// 根据文件大小设置不同的压缩质量
if ((1024 * 1024 * 0.1) <= file.getSize() && file.getSize() <= (1024 * 1024)) {
quality = 0.4f; // 小于1M的图片
} else if ((1024 * 1024) < file.getSize() && file.getSize() <= (1024 * 1024 * 2)) {
quality = 0.2f; // 1-2M的图片
} else {
quality = 0.1f; // 2M以上的图片
}
// 直接在内存中压缩图片,避免使用临时文件
file = PictureUtils.compressImage(inputStream, file.getOriginalFilename(), quality);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
FileInfo fileInfo = fileService.upload(file, savePath, null, false, false);
return FileUploadResp.builder()
.id(fileInfo.getId())
.url(fileInfo.getUrl())
.thUrl(fileInfo.getThUrl())
.metadata(fileInfo.getMetadata())
.build();
}
@Operation(summary = "查询部门树", description = "查询树结构的部门列表")
@GetMapping("/tree/dept")
public List<Tree<Long>> listDeptTree(DeptQuery query, SortQuery sortQuery) {
return deptService.tree(query, sortQuery, true);
}
@Operation(summary = "查询菜单树", description = "查询树结构的菜单列表")
@GetMapping("/tree/menu")
public List<Tree<Long>> listMenuTree(MenuQuery query, SortQuery sortQuery) {
return menuService.tree(query, sortQuery, true);
}
@Operation(summary = "查询用户字典", description = "查询用户字典列表")
@GetMapping("/dict/user")
public List<LabelValueResp> listUserDict(UserQuery query, SortQuery sortQuery) {
return userService.listDict(query, sortQuery);
}
@Operation(summary = "查询角色字典", description = "查询角色字典列表")
@GetMapping("/dict/role")
public List<LabelValueResp> listRoleDict(RoleQuery query, SortQuery sortQuery) {
return roleService.listDict(query, sortQuery);
}
@Operation(summary = "查询字典", description = "查询字典列表")
@Parameter(name = "code", description = "字典编码", example = "notice_type", in = ParameterIn.PATH)
@GetMapping("/dict/{code}")
public List<LabelValueResp> listDict(@PathVariable String code) {
return dictItemService.listByDictCode(code);
}
@SaIgnore
@Operation(summary = "查询系统配置参数", description = "查询系统配置参数")
@GetMapping("/dict/option/site")
@Cached(key = "'SITE'", name = CacheConstants.OPTION_KEY_PREFIX)
public List<LabelValueResp<String>> listSiteOptionDict() {
OptionQuery optionQuery = new OptionQuery();
optionQuery.setCategory(OptionCategoryEnum.SITE.name());
return optionService.list(optionQuery)
.stream()
.map(option -> new LabelValueResp<>(option.getCode(), StrUtil.nullToDefault(option.getValue(), option
.getDefaultValue())))
.toList();
}
}

View File

@@ -0,0 +1,49 @@
package top.wms.admin.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.auth.model.query.OnlineUserQuery;
import top.wms.admin.auth.model.resp.OnlineUserResp;
import top.wms.admin.auth.service.OnlineUserService;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
/**
* 在线用户 API
*
* @author Charles7c
* @since 2023/1/20 21:51
*/
@Tag(name = "在线用户 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/monitor/online")
public class OnlineUserController {
private final OnlineUserService baseService;
@Operation(summary = "分页查询列表", description = "分页查询列表")
@SaCheckPermission("monitor:online:list")
@GetMapping
public PageResp<OnlineUserResp> page(OnlineUserQuery query, @Validated PageQuery pageQuery) {
return baseService.page(query, pageQuery);
}
@Operation(summary = "强退在线用户", description = "强退在线用户")
@Parameter(name = "token", description = "令牌", example = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiTUd6djdyOVFoeHEwdVFqdFAzV3M5YjVJRzh4YjZPSEUifQ.7q7U3ouoN7WPhH2kUEM7vPe5KF3G_qavSG-vRgIxKvE", in = ParameterIn.PATH)
@SaCheckPermission("monitor:online:kickout")
@DeleteMapping("/{token}")
public void kickout(@PathVariable String token) {
String currentToken = StpUtil.getTokenValue();
CheckUtils.throwIfEqual(token, currentToken, "不能强退自己");
StpUtil.kickoutByTokenValue(token);
}
}

View File

@@ -0,0 +1,139 @@
package top.wms.admin.controller.schedule;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.common.log.SnailJobLog;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.system.mapper.*;
import top.wms.admin.system.model.entity.*;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import java.util.List;
import java.util.function.BooleanSupplier;
/**
* 演示环境任务(任务示例)
*
* @author Charles7c
* @since 2024/8/4 15:30
*/
@Component
@RequiredArgsConstructor
public class DemoEnvironmentJob {
private final DictItemMapper dictItemMapper;
private final DictMapper dictMapper;
private final StorageMapper storageMapper;
private final UserMapper userMapper;
private final UserRoleMapper userRoleMapper;
private final UserSocialMapper userSocialMapper;
private final RoleMapper roleMapper;
private final RoleDeptMapper roleDeptMapper;
private final RoleMenuMapper roleMenuMapper;
private final MenuMapper menuMapper;
private final DeptMapper deptMapper;
private final ClientMapper clientsMapper;
private static final Long DELETE_FLAG = 10000L;
private static final Long MESSAGE_FLAG = 0L;
private static final List<Long> USER_FLAG = List
.of(1L, 547889293968801822L, 547889293968801823L, 547889293968801824L, 547889293968801825L, 547889293968801826L, 547889293968801827L, 547889293968801828L, 547889293968801829L, 547889293968801830L, 547889293968801831L);
private static final List<Long> ROLE_FLAG = List.of(1L, 547888897925840927L, 547888897925840928L);
private static final Long DEPT_FLAG = 547887852587843611L;
/**
* 重置演示环境数据
*/
@JobExecutor(name = "ResetEnvironmentData")
@Transactional(rollbackFor = Exception.class)
public void resetEnvironmentData() {
try {
SnailJobLog.REMOTE.info("定时任务 [重置演示环境数据] 开始执行。");
// 检测待清理数据
SnailJobLog.REMOTE.info("开始检测演示环境待清理数据项,请稍候...");
Long dictItemCount = dictItemMapper.lambdaQuery().gt(DictItemDO::getId, DELETE_FLAG).count();
this.log(dictItemCount, "字典项");
Long dictCount = dictMapper.lambdaQuery().gt(DictDO::getId, DELETE_FLAG).count();
this.log(dictCount, "字典");
Long storageCount = storageMapper.lambdaQuery().gt(StorageDO::getId, DELETE_FLAG).count();
this.log(storageCount, "存储");
Long userCount = userMapper.lambdaQuery().notIn(UserDO::getId, USER_FLAG).count();
this.log(userCount, "用户");
Long roleCount = roleMapper.lambdaQuery().notIn(RoleDO::getId, ROLE_FLAG).count();
this.log(roleCount, "角色");
Long menuCount = menuMapper.lambdaQuery().gt(MenuDO::getId, DELETE_FLAG).count();
this.log(menuCount, "菜单");
Long deptCount = deptMapper.lambdaQuery().gt(DeptDO::getId, DEPT_FLAG).count();
this.log(deptCount, "部门");
Long clientCount = clientsMapper.lambdaQuery().gt(ClientDO::getId, DELETE_FLAG).count();
this.log(clientCount, "终端");
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().blockAttack(true).build());
SnailJobLog.REMOTE.info("演示环境待清理数据项检测完成,开始执行清理。");
userRoleMapper.lambdaUpdate().notIn(UserRoleDO::getRoleId, ROLE_FLAG).remove();
userRoleMapper.lambdaUpdate().notIn(UserRoleDO::getUserId, USER_FLAG).remove();
roleDeptMapper.lambdaUpdate().notIn(RoleDeptDO::getRoleId, ROLE_FLAG).remove();
roleMenuMapper.lambdaUpdate().notIn(RoleMenuDO::getRoleId, ROLE_FLAG).remove();
userSocialMapper.lambdaUpdate().notIn(UserSocialDO::getUserId, USER_FLAG).remove();
// 清理具体数据
this.clean(dictItemCount, "字典项", null, () -> dictItemMapper.lambdaUpdate()
.gt(DictItemDO::getId, DELETE_FLAG)
.remove());
this.clean(dictCount, "字典", CacheConstants.DICT_KEY_PREFIX, () -> dictMapper.lambdaUpdate()
.gt(DictDO::getId, DELETE_FLAG)
.remove());
this.clean(storageCount, "存储", null, () -> storageMapper.lambdaUpdate()
.gt(StorageDO::getId, DELETE_FLAG)
.remove());
this.clean(userCount, "用户", null, () -> userMapper.lambdaUpdate().notIn(UserDO::getId, USER_FLAG).remove());
this.clean(roleCount, "角色", null, () -> roleMapper.lambdaUpdate().notIn(RoleDO::getId, ROLE_FLAG).remove());
this.clean(menuCount, "菜单", CacheConstants.ROLE_MENU_KEY_PREFIX, () -> menuMapper.lambdaUpdate()
.gt(MenuDO::getId, DELETE_FLAG)
.remove());
this.clean(deptCount, "部门", null, () -> deptMapper.lambdaUpdate().gt(DeptDO::getId, DEPT_FLAG).remove());
this.clean(clientCount, "终端", null, () -> clientsMapper.lambdaUpdate()
.gt(ClientDO::getId, DEPT_FLAG)
.remove());
SnailJobLog.REMOTE.info("演示环境数据已清理完成。");
SnailJobLog.REMOTE.info("定时任务 [重置演示环境数据] 执行结束。");
} finally {
InterceptorIgnoreHelper.clearIgnoreStrategy();
}
}
/**
* 输出日志
*
* @param count 待清理数据项数量
* @param resource 资源名称
*/
private void log(Long count, String resource) {
if (count > 0) {
SnailJobLog.REMOTE.info("检测到 [{}] 待清理数据项:{}条", resource, count);
}
}
/**
* 清理数据
*
* @param count 待清理数据项数量
* @param resource 资源名称
* @param cacheKey 缓存键
* @param supplier 清理数据项函数
*/
private void clean(Long count, String resource, String cacheKey, BooleanSupplier supplier) {
if (count > 0 && supplier.getAsBoolean()) {
SnailJobLog.REMOTE.info("[{}] 数据项清理完成。", resource);
if (StrUtil.isNotBlank(cacheKey)) {
RedisUtils.deleteByPattern(cacheKey + StringConstants.ASTERISK);
SnailJobLog.REMOTE.info("[{}] 数据项缓存清理完成。", resource);
}
}
}
}

View File

@@ -0,0 +1,90 @@
package top.wms.admin.controller.schedule;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.schedule.model.query.JobQuery;
import top.wms.admin.schedule.model.req.JobReq;
import top.wms.admin.schedule.model.req.JobStatusReq;
import top.wms.admin.schedule.model.resp.JobResp;
import top.wms.admin.schedule.service.JobService;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
import top.continew.starter.log.annotation.Log;
import java.util.List;
/**
* 任务 API
*
* @author KAI
* @author Charles7c
* @since 2024/6/25 22:24
*/
@Tag(name = " 任务 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/schedule/job")
public class JobController {
private final JobService baseService;
@Operation(summary = "分页查询任务列表", description = "分页查询任务列表")
@SaCheckPermission("schedule:job:list")
@GetMapping
public PageResp<JobResp> page(JobQuery query) {
return baseService.page(query);
}
@Operation(summary = "新增任务", description = "新增任务")
@SaCheckPermission("schedule:job:add")
@PostMapping
public void add(@Validated(CrudValidationGroup.Add.class) @RequestBody JobReq req) {
baseService.add(req);
}
@Operation(summary = "修改任务", description = "修改任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:job:update")
@PutMapping("/{id}")
public void update(@Validated(CrudValidationGroup.Update.class) @RequestBody JobReq req, @PathVariable Long id) {
baseService.update(req, id);
}
@Operation(summary = "修改任务状态", description = "修改任务状态")
@SaCheckPermission("schedule:job:update")
@PatchMapping("/{id}/status")
public void updateStatus(@Validated @RequestBody JobStatusReq req, @PathVariable Long id) {
baseService.updateStatus(req, id);
}
@Operation(summary = "删除任务", description = "删除任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:job:delete")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
baseService.delete(id);
}
@Operation(summary = "执行任务", description = "执行任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:job:trigger")
@PostMapping("/trigger/{id}")
public void trigger(@PathVariable Long id) {
baseService.trigger(id);
}
@Log(ignore = true)
@Operation(summary = "查询任务分组列表", description = "查询任务分组列表")
@SaCheckPermission("schedule:job:list")
@GetMapping("/group")
public List<String> listGroup() {
return baseService.listGroup();
}
}

View File

@@ -0,0 +1,74 @@
package top.wms.admin.controller.schedule;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.schedule.model.JobInstanceLogPageResult;
import top.wms.admin.schedule.model.query.JobInstanceLogQuery;
import top.wms.admin.schedule.model.query.JobInstanceQuery;
import top.wms.admin.schedule.model.query.JobLogQuery;
import top.wms.admin.schedule.model.resp.JobInstanceResp;
import top.wms.admin.schedule.model.resp.JobLogResp;
import top.wms.admin.schedule.service.JobLogService;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.util.List;
/**
* 任务日志 API
*
* @author KAI
* @author Charles7c
* @since 2024/6/27 22:24
*/
@Tag(name = " 任务日志 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/schedule/log")
public class JobLogController {
private final JobLogService baseService;
@Operation(summary = "分页查询任务日志列表", description = "分页查询任务日志列表")
@SaCheckPermission("schedule:log:list")
@GetMapping
public PageResp<JobLogResp> page(JobLogQuery query) {
return baseService.page(query);
}
@Operation(summary = "停止任务", description = "停止任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:log:stop")
@PostMapping("/stop/{id}")
public void stop(@PathVariable Long id) {
baseService.stop(id);
}
@Operation(summary = "重试任务", description = "重试任务")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("schedule:log:retry")
@PostMapping("/retry/{id}")
public void retry(@PathVariable Long id) {
baseService.retry(id);
}
@Operation(summary = "查询任务实例列表", description = "查询任务实例列表")
@SaCheckPermission("schedule:log:list")
@GetMapping("/instance")
public List<JobInstanceResp> listInstance(JobInstanceQuery query) {
return baseService.listInstance(query);
}
@Operation(summary = "分页查询任务实例日志列表", description = "分页查询任务实例日志列表")
@SaCheckPermission("schedule:log:list")
@GetMapping("/instance/log")
public JobInstanceLogPageResult pageInstanceLog(JobInstanceLogQuery query) {
return baseService.pageInstanceLog(query);
}
}

View File

@@ -0,0 +1,23 @@
package top.wms.admin.controller.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.ClientQuery;
import top.wms.admin.system.model.req.ClientReq;
import top.wms.admin.system.model.resp.ClientResp;
import top.wms.admin.system.service.ClientService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
/**
* 终端管理 API
*
* @author KAI
* @since 2024/12/03 16:04
*/
@Tag(name = "终端管理 API")
@RestController
@CrudRequestMapping(value = "/system/client", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE})
public class ClientController extends BaseController<ClientService, ClientResp, ClientResp, ClientQuery, ClientReq> {
}

View File

@@ -0,0 +1,27 @@
package top.wms.admin.controller.system;
import top.continew.starter.extension.crud.enums.Api;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.ConfigQuery;
import top.wms.admin.system.model.req.ConfigReq;
import top.wms.admin.system.model.resp.ConfigResp;
import top.wms.admin.system.service.ConfigService;
/**
* 参数配置管理 API
*
* @author zc
* @since 2025/12/30 12:44
*/
@Tag(name = "参数配置管理 API")
@RestController
@CrudRequestMapping(value = "/system/config", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class ConfigController extends BaseController<ConfigService, ConfigResp, ConfigResp, ConfigQuery, ConfigReq> {
}

View File

@@ -0,0 +1,23 @@
package top.wms.admin.controller.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.DeptQuery;
import top.wms.admin.system.model.req.DeptReq;
import top.wms.admin.system.model.resp.DeptResp;
import top.wms.admin.system.service.DeptService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
/**
* 部门管理 API
*
* @author Charles7c
* @since 2023/1/22 17:50
*/
@Tag(name = "部门管理 API")
@RestController
@CrudRequestMapping(value = "/system/dept", api = {Api.TREE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class DeptController extends BaseController<DeptService, DeptResp, DeptResp, DeptQuery, DeptReq> {
}

View File

@@ -0,0 +1,36 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.DictQuery;
import top.wms.admin.system.model.req.DictReq;
import top.wms.admin.system.model.resp.DictResp;
import top.wms.admin.system.service.DictService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
/**
* 字典管理 API
*
* @author Charles7c
* @since 2023/9/11 21:29
*/
@Tag(name = "字典管理 API")
@RestController
@CrudRequestMapping(value = "/system/dict", api = {Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE})
public class DictController extends BaseController<DictService, DictResp, DictResp, DictQuery, DictReq> {
@Operation(summary = "清除缓存", description = "清除缓存")
@SaCheckPermission("system:dict:clearCache")
@DeleteMapping("/cache/{code}")
public void clearCache(@PathVariable String code) {
RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + code);
}
}

View File

@@ -0,0 +1,25 @@
package top.wms.admin.controller.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.DictItemQuery;
import top.wms.admin.system.model.req.DictItemReq;
import top.wms.admin.system.model.resp.DictItemResp;
import top.wms.admin.system.service.DictItemService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.log.annotation.Log;
/**
* 字典项管理 API
*
* @author Charles7c
* @since 2023/9/11 21:29
*/
@Log(module = "字典管理")
@Tag(name = "字典项管理 API")
@RestController
@CrudRequestMapping(value = "/system/dict/item", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE})
public class DictItemController extends BaseController<DictItemService, DictItemResp, DictItemResp, DictItemQuery, DictItemReq> {
}

View File

@@ -0,0 +1,38 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.FileQuery;
import top.wms.admin.system.model.req.FileReq;
import top.wms.admin.system.model.resp.file.FileResp;
import top.wms.admin.system.model.resp.file.FileStatisticsResp;
import top.wms.admin.system.service.FileService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.log.annotation.Log;
/**
* 文件管理 API
*
* @author Charles7c
* @since 2023/12/23 10:38
*/
@Tag(name = "文件管理 API")
@RestController
@RequiredArgsConstructor
@CrudRequestMapping(value = "/system/file", api = {Api.PAGE, Api.UPDATE, Api.DELETE})
public class FileController extends BaseController<FileService, FileResp, FileResp, FileQuery, FileReq> {
@Log(ignore = true)
@Operation(summary = "查询文件资源统计", description = "查询文件资源统计")
@SaCheckPermission("system:file:list")
@GetMapping("/statistics")
public FileStatisticsResp statistics() {
return baseService.statistics();
}
}

View File

@@ -0,0 +1,68 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.system.model.query.LogQuery;
import top.wms.admin.system.model.resp.log.LogDetailResp;
import top.wms.admin.system.model.resp.log.LogResp;
import top.wms.admin.system.service.LogService;
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;
/**
* 系统日志 API
*
* @author Charles7c
* @since 2023/1/18 23:55
*/
@Tag(name = "系统日志 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/log")
public class LogController {
private final LogService baseService;
@Operation(summary = "分页查询列表", description = "分页查询列表")
@SaCheckPermission("monitor:log:list")
@GetMapping
public PageResp<LogResp> page(LogQuery query, @Validated PageQuery pageQuery) {
return baseService.page(query, pageQuery);
}
@Operation(summary = "查询详情", description = "查询详情")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("monitor:log:detail")
@GetMapping("/{id}")
public LogDetailResp get(@PathVariable Long id) {
return baseService.get(id);
}
@ExcludeFromGracefulResponse
@Operation(summary = "导出登录日志", description = "导出登录日志")
@SaCheckPermission("monitor:log:export")
@GetMapping("/export/login")
public void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
baseService.exportLoginLog(query, sortQuery, response);
}
@ExcludeFromGracefulResponse
@Operation(summary = "导出操作日志", description = "导出操作日志")
@SaCheckPermission("monitor:log:export")
@GetMapping("/export/operation")
public void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
baseService.exportOperationLog(query, sortQuery, response);
}
}

View File

@@ -0,0 +1,64 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.MenuQuery;
import top.wms.admin.system.model.req.MenuReq;
import top.wms.admin.system.model.resp.MenuResp;
import top.wms.admin.system.service.MenuService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.URLUtils;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudApi;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import java.lang.reflect.Method;
/**
* 菜单管理 API
*
* @author Charles7c
* @since 2023/2/15 20:35
*/
@Tag(name = "菜单管理 API")
@RestController
@CrudRequestMapping(value = "/system/menu", api = {Api.TREE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE})
public class MenuController extends BaseController<MenuService, MenuResp, MenuResp, MenuQuery, MenuReq> {
@Operation(summary = "清除缓存", description = "清除缓存")
@SaCheckPermission("system:menu:clearCache")
@DeleteMapping("/cache")
public void clearCache() {
RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK);
}
@Override
public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class<?> targetClass) throws Exception {
super.preHandle(crudApi, args, targetMethod, targetClass);
Api api = crudApi.value();
if (!(Api.ADD.equals(api) || Api.UPDATE.equals(api))) {
return;
}
MenuReq req = (MenuReq)args[0];
Boolean isExternal = ObjectUtil.defaultIfNull(req.getIsExternal(), false);
String path = req.getPath();
ValidationUtils.throwIf(Boolean.TRUE.equals(isExternal) && !URLUtils
.isHttpUrl(path), "路由地址格式错误,请以 http:// 或 https:// 开头");
// 非外链菜单参数修正
if (Boolean.FALSE.equals(isExternal)) {
ValidationUtils.throwIf(URLUtils.isHttpUrl(path), "路由地址格式错误");
req.setPath(StrUtil.isBlank(path) ? path : StrUtil.prependIfMissing(path, StringConstants.SLASH));
req.setName(StrUtil.removePrefix(req.getName(), StringConstants.SLASH));
req.setComponent(StrUtil.removePrefix(req.getComponent(), StringConstants.SLASH));
}
}
}

View File

@@ -0,0 +1,53 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.system.model.query.OptionQuery;
import top.wms.admin.system.model.req.OptionReq;
import top.wms.admin.system.model.req.OptionResetValueReq;
import top.wms.admin.system.model.resp.OptionResp;
import top.wms.admin.system.service.OptionService;
import java.util.List;
/**
* 参数管理 API
*
* @author Bull-BCLS
* @since 2023/8/26 19:38
*/
@Tag(name = "参数管理 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/option")
public class OptionController {
private final OptionService baseService;
@Operation(summary = "查询参数列表", description = "查询参数列表")
@SaCheckPermission("system:config:list")
@GetMapping
public List<OptionResp> list(@Validated OptionQuery query) {
return baseService.list(query);
}
@Operation(summary = "修改参数", description = "修改参数")
@SaCheckPermission("system:config:update")
@PutMapping
public void update(@Valid @RequestBody List<OptionReq> options) {
baseService.update(options);
}
@Operation(summary = "重置参数", description = "重置参数")
@SaCheckPermission("system:config:reset")
@PatchMapping("/value")
public void resetValue(@Validated @RequestBody OptionResetValueReq req) {
baseService.resetValue(req);
}
}

View File

@@ -0,0 +1,84 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.system.model.query.RoleQuery;
import top.wms.admin.system.model.query.RoleUserQuery;
import top.wms.admin.system.model.req.RoleReq;
import top.wms.admin.system.model.req.RoleUpdatePermissionReq;
import top.wms.admin.system.model.resp.role.RoleDetailResp;
import top.wms.admin.system.model.resp.role.RoleResp;
import top.wms.admin.system.model.resp.role.RoleUserResp;
import top.wms.admin.system.service.RoleService;
import top.wms.admin.system.service.UserRoleService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.util.List;
/**
* 角色管理 API
*
* @author Charles7c
* @since 2023/2/8 23:11
*/
@Tag(name = "角色管理 API")
@Validated
@RestController
@RequiredArgsConstructor
@CrudRequestMapping(value = "/system/role", api = {Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE})
public class RoleController extends BaseController<RoleService, RoleResp, RoleDetailResp, RoleQuery, RoleReq> {
private final UserRoleService userRoleService;
@Operation(summary = "修改权限", description = "修改角色的功能权限")
@SaCheckPermission("system:role:updatePermission")
@PutMapping("/{id}/permission")
public void updatePermission(@PathVariable("id") Long id, @Validated @RequestBody RoleUpdatePermissionReq req) {
baseService.updatePermission(id, req);
}
@Operation(summary = "分页查询关联用户", description = "分页查询角色关联的用户列表")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:role:list")
@GetMapping("/{id}/user")
public PageResp<RoleUserResp> pageUser(@PathVariable("id") Long id,
@Validated RoleUserQuery query,
@Validated PageQuery pageQuery) {
query.setRoleId(id);
return userRoleService.pageUser(query, pageQuery);
}
@Operation(summary = "分配用户", description = "批量分配角色给用户")
@SaCheckPermission("system:role:assign")
@PostMapping("/{id}/user")
public void assignToUsers(@PathVariable("id") Long id,
@Validated @NotEmpty(message = "用户ID列表不能为空") @RequestBody List<Long> userIds) {
baseService.assignToUsers(id, userIds);
}
@Operation(summary = "取消分配用户", description = "批量取消分配角色给用户")
@SaCheckPermission("system:role:unassign")
@DeleteMapping("/user")
public void unassignFromUsers(@Validated @NotEmpty(message = "用户列表不能为空") @RequestBody List<Long> userRoleIds) {
userRoleService.deleteByIds(userRoleIds);
}
@Operation(summary = "查询关联用户ID", description = "查询角色关联的用户ID列表")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:role:list")
@GetMapping("/{id}/user/id")
public List<Long> listUserId(@PathVariable("id") Long id) {
return userRoleService.listUserIdByRoleId(id);
}
}

View File

@@ -0,0 +1,49 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.common.model.req.CommonStatusUpdateReq;
import top.wms.admin.system.model.query.StorageQuery;
import top.wms.admin.system.model.req.StorageReq;
import top.wms.admin.system.model.resp.StorageResp;
import top.wms.admin.system.service.StorageService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
/**
* 存储管理 API
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Tag(name = "存储管理 API")
@Validated
@RestController
@CrudRequestMapping(value = "/system/storage", api = {Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE})
public class StorageController extends BaseController<StorageService, StorageResp, StorageResp, StorageQuery, StorageReq> {
@Operation(summary = "修改状态", description = "修改状态")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:storage:updateStatus")
@PutMapping({"/{id}/status"})
public void updateStatus(@Validated @RequestBody CommonStatusUpdateReq req, @PathVariable("id") Long id) {
baseService.updateStatus(req, id);
}
@Operation(summary = "设为默认存储", description = "设为默认存储")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:storage:setDefault")
@PutMapping({"/{id}/default"})
public void setDefault(@PathVariable("id") Long id) {
baseService.setDefault(id);
}
}

View File

@@ -0,0 +1,132 @@
package top.wms.admin.controller.system;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.common.util.SecureUtils;
import top.wms.admin.system.enums.SocialSourceEnum;
import top.wms.admin.system.model.entity.UserSocialDO;
import top.wms.admin.system.model.req.user.UserBasicInfoUpdateReq;
import top.wms.admin.system.model.req.user.UserEmailUpdateRequest;
import top.wms.admin.system.model.req.user.UserPasswordUpdateReq;
import top.wms.admin.system.model.req.user.UserPhoneUpdateReq;
import top.wms.admin.system.model.resp.AvatarResp;
import top.wms.admin.system.model.resp.user.UserSocialBindResp;
import top.wms.admin.system.service.UserService;
import top.wms.admin.system.service.UserSocialService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.validation.ValidationUtils;
import java.io.IOException;
import java.util.List;
/**
* 个人中心 API
*
* @author Charles7c
* @since 2023/1/2 11:41
*/
@Tag(name = "个人中心 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/user")
public class UserCenterController {
private static final String DECRYPT_FAILED = "当前密码解密失败";
private static final String CAPTCHA_EXPIRED = "验证码已失效";
private final UserService userService;
private final UserSocialService userSocialService;
@Operation(summary = "修改头像", description = "用户修改个人头像")
@PostMapping("/avatar")
public AvatarResp updateAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) throws IOException {
ValidationUtils.throwIf(avatarFile::isEmpty, "头像不能为空");
String newAvatar = userService.updateAvatar(avatarFile, UserContextHolder.getUserId());
return AvatarResp.builder().avatar(newAvatar).build();
}
@Operation(summary = "修改基础信息", description = "修改用户基础信息")
@PatchMapping("/basic/info")
public void updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) {
userService.updateBasicInfo(req, UserContextHolder.getUserId());
}
@Operation(summary = "修改密码", description = "修改用户登录密码")
@PatchMapping("/password")
public void updatePassword(@Validated @RequestBody UserPasswordUpdateReq updateReq) {
String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getOldPassword()));
ValidationUtils.throwIfNull(rawOldPassword, DECRYPT_FAILED);
String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getNewPassword()));
ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败");
userService.updatePassword(rawOldPassword, rawNewPassword, UserContextHolder.getUserId());
}
@Operation(summary = "修改手机号", description = "修改手机号")
@PatchMapping("/phone")
public void updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) {
String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getOldPassword()));
ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED);
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getPhone();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey);
userService.updatePhone(updateReq.getPhone(), rawOldPassword, UserContextHolder.getUserId());
}
@Operation(summary = "修改邮箱", description = "修改用户邮箱")
@PatchMapping("/email")
public void updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) {
String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getOldPassword()));
ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED);
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getEmail();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey);
userService.updateEmail(updateReq.getEmail(), rawOldPassword, UserContextHolder.getUserId());
}
@Operation(summary = "查询绑定的三方账号", description = "查询绑定的三方账号")
@GetMapping("/social")
public List<UserSocialBindResp> listSocialBind() {
List<UserSocialDO> userSocialList = userSocialService.listByUserId(UserContextHolder.getUserId());
return userSocialList.stream().map(userSocial -> {
String source = userSocial.getSource();
UserSocialBindResp userSocialBind = new UserSocialBindResp();
userSocialBind.setSource(source);
userSocialBind.setDescription(SocialSourceEnum.valueOf(source).getDescription());
return userSocialBind;
}).toList();
}
@Operation(summary = "绑定三方账号", description = "绑定三方账号")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@PostMapping("/social/{source}")
public void bindSocial(@PathVariable String source, @RequestBody Object callback) {
// 第三方登录已禁用
throw new BadRequestException("第三方登录功能已禁用");
}
@Operation(summary = "解绑三方账号", description = "解绑三方账号")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@DeleteMapping("/social/{source}")
public void unbindSocial(@PathVariable String source) {
userSocialService.deleteBySourceAndUserId(source, UserContextHolder.getUserId());
}
}

View File

@@ -0,0 +1,107 @@
package top.wms.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ReUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.wms.admin.common.controller.BaseController;
import top.wms.admin.common.constant.RegexConstants;
import top.wms.admin.common.util.SecureUtils;
import top.wms.admin.system.model.query.UserQuery;
import top.wms.admin.system.model.req.user.UserImportReq;
import top.wms.admin.system.model.req.user.UserPasswordResetReq;
import top.wms.admin.system.model.req.user.UserReq;
import top.wms.admin.system.model.req.user.UserRoleUpdateReq;
import top.wms.admin.system.model.resp.user.UserDetailResp;
import top.wms.admin.system.model.resp.user.UserImportParseResp;
import top.wms.admin.system.model.resp.user.UserImportResp;
import top.wms.admin.system.model.resp.user.UserResp;
import top.wms.admin.system.service.UserService;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
import java.io.IOException;
/**
* 用户管理 API
*
* @author Charles7c
* @since 2023/2/20 21:00
*/
@Tag(name = "用户管理 API")
@Validated
@RestController
@RequiredArgsConstructor
@CrudRequestMapping(value = "/system/user", api = {Api.PAGE, Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE,
Api.EXPORT})
public class UserController extends BaseController<UserService, UserResp, UserDetailResp, UserQuery, UserReq> {
@Override
@Operation(summary = "新增数据", description = "新增数据")
public BaseIdResp<Long> add(@Validated(CrudValidationGroup.Add.class) @RequestBody UserReq req) {
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
req.setPassword(rawPassword);
return super.add(req);
}
@Operation(summary = "下载导入模板", description = "下载导入模板")
@SaCheckPermission("system:user:import")
@GetMapping(value = "/import/template", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadImportTemplate(HttpServletResponse response) throws IOException {
baseService.downloadImportTemplate(response);
}
@Operation(summary = "解析导入数据", description = "解析导入数据")
@SaCheckPermission("system:user:import")
@PostMapping("/import/parse")
public UserImportParseResp parseImport(@NotNull(message = "文件不能为空") MultipartFile file) {
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
return baseService.parseImport(file);
}
@Operation(summary = "导入数据", description = "导入数据")
@SaCheckPermission("system:user:import")
@PostMapping(value = "/import")
public UserImportResp importUser(@Validated @RequestBody UserImportReq req) {
return baseService.importUser(req);
}
@Operation(summary = "重置密码", description = "重置用户登录密码")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:user:resetPwd")
@PatchMapping("/{id}/password")
public void resetPassword(@Validated @RequestBody UserPasswordResetReq req, @PathVariable Long id) {
String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getNewPassword()));
ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawNewPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
req.setNewPassword("{bcrypt}" + encoder.encode(rawNewPassword));
baseService.resetPassword(req, id);
}
@Operation(summary = "分配角色", description = "为用户新增或移除角色")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:user:updateRole")
@PatchMapping("/{id}/role")
public void updateRole(@Validated @RequestBody UserRoleUpdateReq updateReq, @PathVariable Long id) {
baseService.updateRole(updateReq, id);
}
}

View File

@@ -0,0 +1,40 @@
package top.wms.admin.controller.system;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import top.wms.admin.common.util.PictureUtils;
// import top.wms.admin.controller.file.SysFileController;
// import top.wms.admin.file.domain.SysFile;
import top.wms.admin.system.model.resp.file.FileUploadResp;
import top.wms.admin.system.service.FileService;
@RestController
@RequestMapping("/test")
public class zctest {
// @Autowired
// private SysFileController fileController;
@Autowired
private FileService fileService;
@PostMapping()
public FileUploadResp test() {
String urls = "http://bpic.588ku.com/element_origin_min_pic/19/03/15/75076c485081d15ed9c224ad3e4ce4a1.jpg";
MultipartFile file = PictureUtils.createMultipartFile(urls, "zctest11111.jpg");
// SysFile sysFile = fileController.uploadMinio(file, "zctest11111");
// return sysFile;
FileInfo fileInfo = fileService.upload(file, "zctest", "minio", false, false);
return FileUploadResp.builder()
.id(fileInfo.getId())
.url(fileInfo.getUrl())
.thUrl(fileInfo.getThUrl())
.metadata(fileInfo.getMetadata())
.build();
}
}