chore: top.charles7c.continew => top.continew

1.groupId 及基础包名调整,更短的包名,优化品牌形象
2.全局代码格式化
This commit is contained in:
2024-04-22 20:29:17 +08:00
parent da3a4e0756
commit 08eeabc47d
238 changed files with 1121 additions and 1154 deletions

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.spring.SpringUtil;
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.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
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.core.constant.PropertiesConstants;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.annotation.EnableCrudRestController;
import top.continew.starter.web.annotation.EnableGlobalExceptionHandler;
import java.net.InetAddress;
/**
* 启动程序
*
* @author Charles7c
* @since 2022/12/8 23:15
*/
@Slf4j
@RestController
@EnableFileStorage
@SpringBootApplication
@RequiredArgsConstructor
@EnableCrudRestController
@EnableGlobalExceptionHandler
@EnableMethodCache(basePackages = "top.continew.admin")
public class ContiNewAdminApplication implements ApplicationRunner {
private final ProjectProperties projectProperties;
private final ServerProperties serverProperties;
public static void main(String[] args) {
SpringApplication.run(ContiNewAdminApplication.class, args);
}
/**
* 访问首页提示
*
* @return /
*/
@Hidden
@SaIgnore
@GetMapping("/")
public String index() {
return "%s service started successfully.".formatted(projectProperties.getName());
}
@Override
public void run(ApplicationArguments args) throws Exception {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
Integer port = serverProperties.getPort();
String contextPath = serverProperties.getServlet().getContextPath();
String baseUrl = URLUtil.normalize("%s:%s%s".formatted(hostAddress, port, contextPath));
log.info("----------------------------------------------");
log.info("{} service started successfully.", projectProperties.getName());
log.info("API 地址:{}", baseUrl);
String docEnabledProperty = PropertiesConstants.SPRINGDOC_SWAGGER_UI + StringConstants.DOT + PropertiesConstants.ENABLED;
if (Boolean.TRUE.equals(SpringUtil.getProperty(docEnabledProperty, boolean.class, false))) {
log.info("API 文档:{}/doc.html", baseUrl);
}
log.info("----------------------------------------------");
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.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 lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.auth.model.req.AccountLoginReq;
import top.continew.admin.auth.model.req.EmailLoginReq;
import top.continew.admin.auth.model.req.PhoneLoginReq;
import top.continew.admin.auth.model.resp.LoginResp;
import top.continew.admin.auth.model.resp.RouteResp;
import top.continew.admin.auth.model.resp.UserInfoResp;
import top.continew.admin.auth.service.LoginService;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.model.resp.UserDetailResp;
import top.continew.admin.system.service.UserService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.web.model.R;
import top.continew.starter.log.core.annotation.Log;
import java.util.List;
/**
* 认证 API
*
* @author Charles7c
* @since 2022/12/21 20:37
*/
@Log(module = "登录")
@Tag(name = "认证 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {
private static final String CAPTCHA_EXPIRED = "验证码已失效";
private static final String CAPTCHA_ERROR = "验证码错误";
private final LoginService loginService;
private final UserService userService;
@SaIgnore
@Operation(summary = "账号登录", description = "根据账号和密码进行登录认证")
@PostMapping("/account")
public R<LoginResp> accountLogin(@Validated @RequestBody AccountLoginReq loginReq) {
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + loginReq.getUuid();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
RedisUtils.delete(captchaKey);
ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR);
// 用户登录
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(loginReq.getPassword()));
ValidationUtils.throwIfBlank(rawPassword, "密码解密失败");
String token = loginService.accountLogin(loginReq.getUsername(), rawPassword);
return R.ok(LoginResp.builder().token(token).build());
}
@SaIgnore
@Operation(summary = "邮箱登录", description = "根据邮箱和验证码进行登录认证")
@PostMapping("/email")
public R<LoginResp> emailLogin(@Validated @RequestBody EmailLoginReq loginReq) {
String email = loginReq.getEmail();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email;
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR);
RedisUtils.delete(captchaKey);
String token = loginService.emailLogin(email);
return R.ok(LoginResp.builder().token(token).build());
}
@SaIgnore
@Operation(summary = "手机号登录", description = "根据手机号和验证码进行登录认证")
@PostMapping("/phone")
public R<LoginResp> phoneLogin(@Validated @RequestBody PhoneLoginReq loginReq) {
String phone = loginReq.getPhone();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone;
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR);
RedisUtils.delete(captchaKey);
String token = loginService.phoneLogin(phone);
return R.ok(LoginResp.builder().token(token).build());
}
@Operation(summary = "用户退出", description = "注销用户的当前登录")
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx", in = ParameterIn.HEADER)
@PostMapping("/logout")
public R<Object> logout() {
Object loginId = StpUtil.getLoginId(-1L);
StpUtil.logout();
return R.ok(loginId);
}
@Log(ignore = true)
@Operation(summary = "获取用户信息", description = "获取登录用户信息")
@GetMapping("/user/info")
public R<UserInfoResp> getUserInfo() {
LoginUser loginUser = LoginHelper.getLoginUser();
UserDetailResp userDetailResp = userService.get(loginUser.getId());
UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class);
userInfoResp.setPermissions(loginUser.getPermissions());
userInfoResp.setRoles(loginUser.getRoleCodes());
return R.ok(userInfoResp);
}
@Log(ignore = true)
@Operation(summary = "获取路由信息", description = "获取登录用户的路由信息")
@GetMapping("/route")
public R<List<RouteResp>> listRoute() {
return R.ok(loginService.buildRouteTree(LoginHelper.getUserId()));
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.auth;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import com.xkcoding.justauth.AuthRequestFactory;
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 me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.auth.model.resp.LoginResp;
import top.continew.admin.auth.model.resp.SocialAuthAuthorizeResp;
import top.continew.admin.auth.service.LoginService;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
/**
* 三方账号认证 API
*
* @author Charles7c
* @since 2023/10/8 22:52
*/
@Log(module = "登录")
@Tag(name = "三方账号认证 API")
@SaIgnore
@RestController
@RequiredArgsConstructor
@RequestMapping("/oauth")
public class SocialAuthController {
private final LoginService loginService;
private final AuthRequestFactory authRequestFactory;
@Operation(summary = "三方账号登录授权", description = "三方账号登录授权")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@GetMapping("/{source}")
public R<SocialAuthAuthorizeResp> authorize(@PathVariable String source) {
AuthRequest authRequest = this.getAuthRequest(source);
return R.ok(SocialAuthAuthorizeResp.builder()
.authorizeUrl(authRequest.authorize(AuthStateUtils.createState()))
.build());
}
@Operation(summary = "三方账号登录", description = "三方账号登录")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@PostMapping("/{source}")
public R<LoginResp> login(@PathVariable String source, @RequestBody AuthCallback callback) {
if (StpUtil.isLogin()) {
StpUtil.logout();
}
AuthRequest authRequest = this.getAuthRequest(source);
AuthResponse<AuthUser> response = authRequest.login(callback);
ValidationUtils.throwIf(!response.ok(), response.getMsg());
AuthUser authUser = response.getData();
String token = loginService.socialLogin(authUser);
return R.ok(LoginResp.builder().token(token).build());
}
private AuthRequest getAuthRequest(String source) {
try {
return authRequestFactory.get(source);
} catch (Exception e) {
throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source));
}
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import com.anji.captcha.model.common.RepCodeEnum;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.wf.captcha.base.Captcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.mail.MessagingException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.RequiredArgsConstructor;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.redisson.api.RateType;
import org.springframework.http.HttpHeaders;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.common.config.properties.CaptchaProperties;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.model.resp.CaptchaResp;
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.core.util.TemplateUtils;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.messaging.mail.util.MailUtils;
import top.continew.starter.web.model.R;
import java.time.Duration;
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 CaptchaService behaviorCaptchaService;
private final GraphicCaptchaService graphicCaptchaService;
private final ProjectProperties projectProperties;
private final CaptchaProperties captchaProperties;
@Log(ignore = true)
@Operation(summary = "获取行为验证码", description = "获取行为验证码Base64编码")
@GetMapping("/behavior")
public R<Object> getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) {
captchaReq.setBrowserInfo(JakartaServletUtil.getClientIP(request) + request.getHeader(HttpHeaders.USER_AGENT));
return R.ok(behaviorCaptchaService.get(captchaReq).getRepData());
}
@Log(ignore = true)
@Operation(summary = "校验行为验证码", description = "校验行为验证码")
@PostMapping("/behavior")
public R<Object> checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq) {
return R.ok(behaviorCaptchaService.check(captchaReq));
}
@Log(ignore = true)
@Operation(summary = "获取图片验证码", description = "获取图片验证码Base64编码带图片格式data:image/gif;base64")
@GetMapping("/img")
public R<CaptchaResp> getImageCaptcha() {
String uuid = IdUtil.fastUUID();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + uuid;
Captcha captcha = graphicCaptchaService.getCaptcha();
RedisUtils.set(captchaKey, captcha.text(), Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
return R.ok(CaptchaResp.builder().uuid(uuid).img(captcha.toBase64()).build());
}
@Operation(summary = "获取邮箱验证码", description = "发送验证码到指定邮箱")
@GetMapping("/mail")
public R<Void> getMailCaptcha(@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email) throws MessagingException {
String limitKeyPrefix = CacheConstants.LIMIT_KEY_PREFIX;
String captchaKeyPrefix = CacheConstants.CAPTCHA_KEY_PREFIX;
String limitCaptchaKey = limitKeyPrefix + captchaKeyPrefix + email;
long limitTimeInMillisecond = RedisUtils.getTimeToLive(limitCaptchaKey);
CheckUtils.throwIf(limitTimeInMillisecond > 0, "发送验证码过于频繁,请您 {}s 后再试", limitTimeInMillisecond / 1000);
// 生成验证码
CaptchaProperties.CaptchaMail captchaMail = captchaProperties.getMail();
String captcha = RandomUtil.randomNumbers(captchaMail.getLength());
// 发送验证码
Long expirationInMinutes = captchaMail.getExpirationInMinutes();
String content = TemplateUtils.render(captchaMail.getTemplatePath(), Dict.create()
.set("captcha", captcha)
.set("expiration", expirationInMinutes));
MailUtils.sendHtml(email, "【%s】邮箱验证码".formatted(projectProperties.getName()), content);
// 保存验证码
String captchaKey = captchaKeyPrefix + email;
RedisUtils.set(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes));
RedisUtils.set(limitCaptchaKey, captcha, Duration.ofSeconds(captchaMail.getLimitInSeconds()));
return R.ok("发送成功,验证码有效期 %s 分钟".formatted(expirationInMinutes));
}
@Operation(summary = "获取短信验证码", description = "发送验证码到指定手机号")
@GetMapping("/sms")
public R<Void> getSmsCaptcha(@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") String phone,
CaptchaVO captchaReq,
HttpServletRequest request) {
// 行为验证码校验
ResponseModel verificationRes = behaviorCaptchaService.verification(captchaReq);
ValidationUtils.throwIfNotEqual(verificationRes.getRepCode(), RepCodeEnum.SUCCESS.getCode(), verificationRes
.getRepMsg());
CaptchaProperties.CaptchaSms captchaSms = captchaProperties.getSms();
String templateId = captchaSms.getTemplateId();
String limitKeyPrefix = CacheConstants.LIMIT_KEY_PREFIX;
String captchaKeyPrefix = CacheConstants.CAPTCHA_KEY_PREFIX;
String limitTemplateKeyPrefix = limitKeyPrefix + captchaKeyPrefix;
// 限制短信发送频率
// 1.同一号码同一短信模板1分钟2条1小时8条24小时20条e.g. LIMIT:CAPTCHA:XXX:188xxxxx:1
final String errorMsg = "获取验证码操作太频繁,请稍后再试";
CheckUtils.throwIf(!RedisUtils.rateLimit(RedisUtils
.formatKey(limitTemplateKeyPrefix + "MIN", phone, templateId), RateType.OVERALL, 2, 60), errorMsg);
CheckUtils.throwIf(!RedisUtils.rateLimit(RedisUtils
.formatKey(limitTemplateKeyPrefix + "HOUR", phone, templateId), RateType.OVERALL, 8, 60 * 60), errorMsg);
CheckUtils.throwIf(!RedisUtils.rateLimit(RedisUtils
.formatKey(limitTemplateKeyPrefix + "DAY", phone, templateId), RateType.OVERALL, 20, 60 * 60 * 24), errorMsg);
// 2.同一号码所有短信模板 24 小时 100 条e.g. LIMIT:CAPTCHA:188xxxxx
String limitPhoneKey = limitKeyPrefix + captchaKeyPrefix + phone;
CheckUtils.throwIf(!RedisUtils.rateLimit(limitPhoneKey, RateType.OVERALL, 100, 60 * 60 * 24), errorMsg);
// 3.同一 IP 每分钟限制发送 30 条e.g. LIMIT:CAPTCHA:PHONE:1xx.1xx.1xx.1xx
String limitIpKey = RedisUtils.formatKey(limitKeyPrefix + captchaKeyPrefix + "PHONE", JakartaServletUtil
.getClientIP(request));
CheckUtils.throwIf(!RedisUtils.rateLimit(limitIpKey, RateType.OVERALL, 30, 60), errorMsg);
// 生成验证码
String captcha = RandomUtil.randomNumbers(captchaSms.getLength());
// 发送验证码
Long expirationInMinutes = captchaSms.getExpirationInMinutes();
SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.CLOOPEN);
Map<String, String> messageMap = MapUtil.newHashMap(2, true);
messageMap.put("captcha", captcha);
messageMap.put("expirationInMinutes", String.valueOf(expirationInMinutes));
SmsResponse smsResponse = smsBlend.sendMessage(phone, captchaSms
.getTemplateId(), (LinkedHashMap<String, String>)messageMap);
CheckUtils.throwIf(!smsResponse.isSuccess(), "验证码发送失败");
// 保存验证码
String captchaKey = captchaKeyPrefix + phone;
RedisUtils.set(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes));
return R.ok("发送成功,验证码有效期 %s 分钟".formatted(expirationInMinutes));
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ClassUtil;
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.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.model.resp.LabelValueResp;
import top.continew.admin.system.model.query.DeptQuery;
import top.continew.admin.system.model.query.MenuQuery;
import top.continew.admin.system.model.query.OptionQuery;
import top.continew.admin.system.model.query.RoleQuery;
import top.continew.admin.system.model.resp.FileUploadResp;
import top.continew.admin.system.service.*;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mybatis.plus.base.IBaseEnum;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* 公共 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 ProjectProperties projectProperties;
private final FileService fileService;
private final DeptService deptService;
private final MenuService menuService;
private final RoleService roleService;
private final DictItemService dictItemService;
private final OptionService optionService;
@Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/file")
public R<FileUploadResp> upload(@NotNull(message = "文件不能为空") MultipartFile file) {
ValidationUtils.throwIf(projectProperties.isProduction(), "演示环境不支持上传文件");
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
FileInfo fileInfo = fileService.upload(file);
return R.ok(FileUploadResp.builder().url(fileInfo.getUrl()).build());
}
@Operation(summary = "查询部门树", description = "查询树结构的部门列表")
@GetMapping("/tree/dept")
public R<List<Tree<Long>>> listDeptTree(DeptQuery query, SortQuery sortQuery) {
return R.ok(deptService.tree(query, sortQuery, true));
}
@Operation(summary = "查询菜单树", description = "查询树结构的菜单列表")
@GetMapping("/tree/menu")
public R<List<Tree<Long>>> listMenuTree(MenuQuery query, SortQuery sortQuery) {
return R.ok(menuService.tree(query, sortQuery, true));
}
@Operation(summary = "查询角色字典", description = "查询角色字典列表")
@GetMapping("/dict/role")
public R<List<LabelValueResp<Long>>> listRoleDict(RoleQuery query, SortQuery sortQuery) {
return R.ok(roleService.buildDict(roleService.list(query, sortQuery)));
}
@Operation(summary = "查询字典", description = "查询字典列表")
@Parameter(name = "code", description = "字典编码", example = "announcement_type", in = ParameterIn.PATH)
@GetMapping("/dict/{code}")
public R<List<LabelValueResp<Serializable>>> listDict(@PathVariable String code) {
Optional<Class<?>> enumClassOptional = this.getEnumClassByName(code);
return R.ok(enumClassOptional.map(this::listEnumDict).orElseGet(() -> dictItemService.listByDictCode(code)));
}
@SaIgnore
@Operation(summary = "查询参数", description = "查询参数")
@GetMapping("/option")
@Cached(key = "#query.code", name = CacheConstants.OPTION_KEY_PREFIX)
public R<List<LabelValueResp<String>>> listOption(@Validated OptionQuery query) {
return R.ok(optionService.list(query)
.stream()
.map(option -> new LabelValueResp<>(option.getCode(), StrUtil.nullToDefault(option.getValue(), option
.getDefaultValue())))
.toList());
}
/**
* 根据枚举类名查询
*
* @param enumClassName 枚举类名
* @return 枚举类型
*/
private Optional<Class<?>> getEnumClassByName(String enumClassName) {
Set<Class<?>> classSet = ClassUtil.scanPackageBySuper(projectProperties.getBasePackage(), IBaseEnum.class);
return classSet.stream()
.filter(c -> StrUtil.equalsAnyIgnoreCase(c.getSimpleName(), enumClassName, StrUtil
.toCamelCase(enumClassName)))
.findFirst();
}
/**
* 查询枚举字典
*
* @param enumClass 枚举类型
* @return 枚举字典
*/
private List<LabelValueResp<Serializable>> listEnumDict(Class<?> enumClass) {
Object[] enumConstants = enumClass.getEnumConstants();
return Arrays.stream(enumConstants).map(e -> {
IBaseEnum baseEnum = (IBaseEnum)e;
return new LabelValueResp<>(baseEnum.getDescription(), baseEnum.getValue(), baseEnum.getColor());
}).toList();
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.common;
import com.alicp.jetcache.anno.CachePenetrationProtect;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
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 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.continew.admin.common.constant.CacheConstants;
import top.continew.admin.system.model.resp.DashboardAccessTrendResp;
import top.continew.admin.system.model.resp.DashboardGeoDistributionResp;
import top.continew.admin.system.model.resp.DashboardPopularModuleResp;
import top.continew.admin.system.model.resp.DashboardTotalResp;
import top.continew.admin.system.service.DashboardService;
import top.continew.admin.system.model.resp.DashboardAnnouncementResp;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.web.model.R;
import top.continew.starter.log.core.annotation.Log;
import java.util.List;
/**
* 仪表盘 API
*
* @author Charles7c
* @since 2023/1/22 21:48
*/
@Tag(name = "仪表盘 API")
@Log(ignore = true)
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/dashboard")
public class DashboardController {
private final DashboardService dashboardService;
@Operation(summary = "查询总计信息", description = "查询总计信息")
@GetMapping("/total")
public R<DashboardTotalResp> getTotal() {
return R.ok(dashboardService.getTotal());
}
@Operation(summary = "查询访问趋势信息", description = "查询访问趋势信息")
@Parameter(name = "days", description = "日期数", example = "30", in = ParameterIn.PATH)
@GetMapping("/access/trend/{days}")
@CachePenetrationProtect
@CacheRefresh(refresh = 7200)
@Cached(key = "#days", cacheType = CacheType.BOTH, name = CacheConstants.DASHBOARD_KEY_PREFIX, syncLocal = true)
public R<List<DashboardAccessTrendResp>> listAccessTrend(@PathVariable Integer days) {
ValidationUtils.throwIf(7 != days && 30 != days, "仅支持查询近 7/30 天访问趋势信息");
return R.ok(dashboardService.listAccessTrend(days));
}
@Operation(summary = "查询热门模块列表", description = "查询热门模块列表")
@GetMapping("/popular/module")
public R<List<DashboardPopularModuleResp>> listPopularModule() {
return R.ok(dashboardService.listPopularModule());
}
@Operation(summary = "查询访客地域分布信息", description = "查询访客地域分布信息")
@GetMapping("/geo/distribution")
public R<DashboardGeoDistributionResp> getGeoDistribution() {
return R.ok(dashboardService.getGeoDistribution());
}
@Operation(summary = "查询公告列表", description = "查询公告列表")
@GetMapping("/announcement")
public R<List<DashboardAnnouncementResp>> listAnnouncement() {
return R.ok(dashboardService.listAnnouncement());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.monitor;
import lombok.RequiredArgsConstructor;
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.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import top.continew.admin.auth.model.query.OnlineUserQuery;
import top.continew.admin.auth.model.resp.OnlineUserResp;
import top.continew.admin.auth.service.OnlineUserService;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
/**
* 在线用户 API
*
* @author Charles7c
* @since 2023/1/20 21:51
*/
@Tag(name = "在线用户 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/monitor/online/user")
public class OnlineUserController {
private final OnlineUserService onlineUserService;
@Operation(summary = "分页查询列表", description = "分页查询列表")
@SaCheckPermission("monitor:online:user:list")
@GetMapping
public R<PageResp<OnlineUserResp>> page(OnlineUserQuery query, @Validated PageQuery pageQuery) {
return R.ok(onlineUserService.page(query, pageQuery));
}
@Operation(summary = "强退在线用户", description = "强退在线用户")
@Parameter(name = "token", description = "令牌", example = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiTUd6djdyOVFoeHEwdVFqdFAzV3M5YjVJRzh4YjZPSEUifQ.7q7U3ouoN7WPhH2kUEM7vPe5KF3G_qavSG-vRgIxKvE", in = ParameterIn.PATH)
@SaCheckPermission("monitor:online:user:delete")
@DeleteMapping("/{token}")
public R<Void> kickout(@PathVariable String token) {
String currentToken = StpUtil.getTokenValue();
CheckUtils.throwIfEqual(token, currentToken, "不能强退自己");
StpUtil.kickoutByTokenValue(token);
return R.ok("强退成功");
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import java.time.LocalDateTime;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import top.continew.admin.system.model.query.AnnouncementQuery;
import top.continew.admin.system.model.req.AnnouncementReq;
import top.continew.admin.system.model.resp.AnnouncementDetailResp;
import top.continew.admin.system.model.resp.AnnouncementResp;
import top.continew.admin.system.service.AnnouncementService;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.web.model.R;
/**
* 公告管理 API
*
* @author Charles7c
* @since 2023/8/20 10:55
*/
@Tag(name = "公告管理 API")
@RestController
@CrudRequestMapping("/system/announcement")
public class AnnouncementController extends BaseController<AnnouncementService, AnnouncementResp, AnnouncementDetailResp, AnnouncementQuery, AnnouncementReq> {
@Override
@SaCheckPermission("system:announcement:add")
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody AnnouncementReq req) {
this.checkTime(req);
return super.add(req);
}
@Override
@SaCheckPermission("system:announcement:update")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody AnnouncementReq req,
@PathVariable Long id) {
this.checkTime(req);
return super.update(req, id);
}
/**
* 检查时间
*
* @param req 创建或修改信息
*/
private void checkTime(AnnouncementReq req) {
LocalDateTime effectiveTime = req.getEffectiveTime();
LocalDateTime terminateTime = req.getTerminateTime();
if (null != effectiveTime && null != terminateTime) {
ValidationUtils.throwIf(terminateTime.isBefore(effectiveTime), "终止时间必须晚于生效时间");
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.system.model.query.DeptQuery;
import top.continew.admin.system.model.req.DeptReq;
import top.continew.admin.system.model.resp.DeptResp;
import top.continew.admin.system.service.DeptService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
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.GET, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class DeptController extends BaseController<DeptService, DeptResp, DeptResp, DeptQuery, DeptReq> {
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.system.model.query.DictQuery;
import top.continew.admin.system.model.req.DictReq;
import top.continew.admin.system.model.resp.DictResp;
import top.continew.admin.system.service.DictService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
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.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE})
public class DictController extends BaseController<DictService, DictResp, DictResp, DictQuery, DictReq> {
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.system.model.query.DictItemQuery;
import top.continew.admin.system.model.req.DictItemReq;
import top.continew.admin.system.model.resp.DictItemResp;
import top.continew.admin.system.service.DictItemService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
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/item", api = {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE})
public class DictItemController extends BaseController<DictItemService, DictItemResp, DictItemResp, DictItemQuery, DictItemReq> {
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.system.model.query.FileQuery;
import top.continew.admin.system.model.req.FileReq;
import top.continew.admin.system.model.resp.FileResp;
import top.continew.admin.system.service.FileService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
/**
* 文件管理 API
*
* @author Charles7c
* @since 2023/12/23 10:38
*/
@Tag(name = "文件管理 API")
@RestController
@CrudRequestMapping(value = "/system/file", api = {Api.PAGE, Api.UPDATE, Api.DELETE})
public class FileController extends BaseController<FileService, FileResp, FileResp, FileQuery, FileReq> {
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.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.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.continew.admin.system.model.query.LogQuery;
import top.continew.admin.system.model.resp.log.LogDetailResp;
import top.continew.admin.system.model.resp.log.LogResp;
import top.continew.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;
import top.continew.starter.web.model.R;
/**
* 系统日志 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 = "分页查询列表")
@GetMapping
public R<PageResp<LogResp>> page(LogQuery query, @Validated PageQuery pageQuery) {
return R.ok(baseService.page(query, pageQuery));
}
@Operation(summary = "查询详情", description = "查询详情")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@GetMapping("/{id}")
public R<LogDetailResp> get(@PathVariable Long id) {
return R.ok(baseService.get(id));
}
@Operation(summary = "导出登录日志", description = "导出登录日志")
@GetMapping("/export/login")
public void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
baseService.exportLoginLog(query, sortQuery, response);
}
@Operation(summary = "导出操作日志", description = "导出操作日志")
@GetMapping("/export/operation")
public void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
baseService.exportOperationLog(query, sortQuery, response);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
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.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.system.model.query.MenuQuery;
import top.continew.admin.system.model.req.MenuReq;
import top.continew.admin.system.model.resp.MenuResp;
import top.continew.admin.system.service.MenuService;
import top.continew.starter.core.util.URLUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.web.model.R;
/**
* 菜单管理 API
*
* @author Charles7c
* @since 2023/2/15 20:35
*/
@Tag(name = "菜单管理 API")
@RestController
@CrudRequestMapping(value = "/system/menu", api = {Api.TREE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE})
public class MenuController extends BaseController<MenuService, MenuResp, MenuResp, MenuQuery, MenuReq> {
@Override
@SaCheckPermission("system:menu:add")
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody MenuReq req) {
this.checkPath(req);
return super.add(req);
}
@Override
@SaCheckPermission("system:menu:update")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody MenuReq req, @PathVariable Long id) {
this.checkPath(req);
return super.update(req, id);
}
/**
* 检查路由地址格式
*
* @param req 创建或修改信息
*/
private void checkPath(MenuReq req) {
Boolean isExternal = ObjectUtil.defaultIfNull(req.getIsExternal(), false);
String path = req.getPath();
ValidationUtils.throwIf(isExternal && !URLUtils.isHttpUrl(path), "路由地址格式错误,请以 http:// 或 https:// 开头");
ValidationUtils.throwIf(!isExternal && URLUtils.isHttpUrl(path), "路由地址格式错误");
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.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 lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.model.query.MessageQuery;
import top.continew.admin.system.model.resp.MessageResp;
import top.continew.admin.system.model.resp.MessageUnreadResp;
import top.continew.admin.system.service.MessageService;
import top.continew.admin.system.service.MessageUserService;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
import top.continew.starter.log.core.annotation.Log;
import java.util.List;
/**
* 消息管理 API
*
* @author Bull-BCLS
* @since 2023/10/15 19:05
*/
@Tag(name = "消息管理 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/message")
public class MessageController {
private final MessageService baseService;
private final MessageUserService messageUserService;
@Operation(summary = "分页查询列表", description = "分页查询列表")
@GetMapping
public R<PageResp<MessageResp>> page(MessageQuery query, @Validated PageQuery pageQuery) {
query.setUserId(LoginHelper.getUserId());
return R.ok(baseService.page(query, pageQuery));
}
@Operation(summary = "删除数据", description = "删除数据")
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
@DeleteMapping("/{ids}")
public R<Void> delete(@PathVariable List<Long> ids) {
baseService.delete(ids);
return R.ok("删除成功");
}
@Operation(summary = "标记已读", description = "将消息标记为已读状态")
@Parameter(name = "ids", description = "消息ID列表", example = "1,2", in = ParameterIn.QUERY)
@PatchMapping("/read")
public R<Void> readMessage(@RequestParam(required = false) List<Long> ids) {
messageUserService.readMessage(ids);
return R.ok();
}
@Log(ignore = true)
@Operation(summary = "查询未读消息数量", description = "查询当前用户的未读消息数量")
@Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY)
@GetMapping("/unread")
public R<MessageUnreadResp> countUnreadMessage(@RequestParam(required = false) Boolean detail) {
return R.ok(messageUserService.countUnreadMessageByUserId(LoginHelper.getUserId(), detail));
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import java.util.List;
import lombok.RequiredArgsConstructor;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import top.continew.admin.system.model.query.OptionQuery;
import top.continew.admin.system.model.req.OptionReq;
import top.continew.admin.system.model.req.OptionResetValueReq;
import top.continew.admin.system.model.resp.OptionResp;
import top.continew.admin.system.service.OptionService;
import top.continew.starter.web.model.R;
/**
* 参数管理 API
*
* @author Bull-BCLS
* @since 2023/8/26 19:38
*/
@Tag(name = "参数管理 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/option")
public class OptionController {
private final OptionService optionService;
@Operation(summary = "查询参数列表", description = "查询参数列表")
@SaCheckPermission("system:config:list")
@GetMapping
public R<List<OptionResp>> list(@Validated OptionQuery query) {
return R.ok(optionService.list(query));
}
@Operation(summary = "修改参数", description = "修改参数")
@SaCheckPermission("system:config:update")
@PatchMapping
public R<Void> update(@Validated @RequestBody List<OptionReq> req) {
optionService.update(req);
return R.ok();
}
@Operation(summary = "重置参数", description = "重置参数")
@SaCheckPermission("system:config:reset")
@PatchMapping("/value")
public R<Void> resetValue(@Validated @RequestBody OptionResetValueReq req) {
optionService.resetValue(req);
return R.ok();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.system.model.query.RoleQuery;
import top.continew.admin.system.model.req.RoleReq;
import top.continew.admin.system.model.resp.RoleDetailResp;
import top.continew.admin.system.model.resp.RoleResp;
import top.continew.admin.system.service.RoleService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
/**
* 角色管理 API
*
* @author Charles7c
* @since 2023/2/8 23:11
*/
@Tag(name = "角色管理 API")
@RestController
@CrudRequestMapping(value = "/system/role", api = {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE})
public class RoleController extends BaseController<RoleService, RoleResp, RoleDetailResp, RoleQuery, RoleReq> {
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.system.model.query.StorageQuery;
import top.continew.admin.system.model.req.StorageReq;
import top.continew.admin.system.model.resp.StorageResp;
import top.continew.admin.system.service.StorageService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
/**
* 存储管理 API
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Tag(name = "存储管理 API")
@RestController
@CrudRequestMapping(value = "/system/storage", api = {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE})
public class StorageController extends BaseController<StorageService, StorageResp, StorageResp, StorageQuery, StorageReq> {
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.system;
import cn.hutool.core.util.ReUtil;
import com.xkcoding.justauth.AuthRequestFactory;
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 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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.admin.common.enums.SocialSourceEnum;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.model.entity.UserSocialDO;
import top.continew.admin.system.model.req.UserBasicInfoUpdateReq;
import top.continew.admin.system.model.req.UserEmailUpdateRequest;
import top.continew.admin.system.model.req.UserPasswordUpdateReq;
import top.continew.admin.system.model.req.UserPhoneUpdateReq;
import top.continew.admin.system.model.resp.AvatarResp;
import top.continew.admin.system.model.resp.UserSocialBindResp;
import top.continew.admin.system.service.UserService;
import top.continew.admin.system.service.UserSocialService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.web.model.R;
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;
private final AuthRequestFactory authRequestFactory;
@Operation(summary = "上传头像", description = "用户上传个人头像")
@PostMapping("/avatar")
public R<AvatarResp> uploadAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) {
ValidationUtils.throwIf(avatarFile::isEmpty, "头像不能为空");
String newAvatar = userService.uploadAvatar(avatarFile, LoginHelper.getUserId());
return R.ok("上传成功", AvatarResp.builder().avatar(newAvatar).build());
}
@Operation(summary = "修改基础信息", description = "修改用户基础信息")
@PatchMapping("/basic/info")
public R<Void> updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) {
userService.updateBasicInfo(req, LoginHelper.getUserId());
return R.ok("修改成功");
}
@Operation(summary = "修改密码", description = "修改用户登录密码")
@PatchMapping("/password")
public R<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, "新密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawNewPassword), "密码长度为 6 到 32 位,可以包含字母、数字、下划线,特殊字符,同时包含字母和数字");
userService.updatePassword(rawOldPassword, rawNewPassword, LoginHelper.getUserId());
return R.ok("修改成功,请牢记你的新密码");
}
@Operation(summary = "修改手机号", description = "修改手机号")
@PatchMapping("/phone")
public R<Void> updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) {
String rawCurrentPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getCurrentPassword()));
ValidationUtils.throwIfBlank(rawCurrentPassword, DECRYPT_FAILED);
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getNewPhone();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey);
userService.updatePhone(updateReq.getNewPhone(), rawCurrentPassword, LoginHelper.getUserId());
return R.ok("修改成功");
}
@Operation(summary = "修改邮箱", description = "修改用户邮箱")
@PatchMapping("/email")
public R<Void> updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) {
String rawCurrentPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getCurrentPassword()));
ValidationUtils.throwIfBlank(rawCurrentPassword, DECRYPT_FAILED);
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getNewEmail();
String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey);
userService.updateEmail(updateReq.getNewEmail(), rawCurrentPassword, LoginHelper.getUserId());
return R.ok("修改成功");
}
@Operation(summary = "查询绑定的三方账号", description = "查询绑定的三方账号")
@GetMapping("/social")
public R<List<UserSocialBindResp>> listSocialBind() {
List<UserSocialDO> userSocialList = userSocialService.listByUserId(LoginHelper.getUserId());
List<UserSocialBindResp> userSocialBindList = userSocialList.stream().map(userSocial -> {
String source = userSocial.getSource();
UserSocialBindResp userSocialBind = new UserSocialBindResp();
userSocialBind.setSource(source);
userSocialBind.setDescription(SocialSourceEnum.valueOf(source).getDescription());
return userSocialBind;
}).toList();
return R.ok(userSocialBindList);
}
@Operation(summary = "绑定三方账号", description = "绑定三方账号")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@PostMapping("/social/{source}")
public R<Void> bindSocial(@PathVariable String source, @RequestBody AuthCallback callback) {
AuthRequest authRequest = authRequestFactory.get(source);
AuthResponse<AuthUser> response = authRequest.login(callback);
ValidationUtils.throwIf(!response.ok(), response.getMsg());
AuthUser authUser = response.getData();
userSocialService.bind(authUser, LoginHelper.getUserId());
return R.ok("绑定成功");
}
@Operation(summary = "解绑三方账号", description = "解绑三方账号")
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
@DeleteMapping("/social/{source}")
public R<Void> unbindSocial(@PathVariable String source) {
userSocialService.deleteBySourceAndUserId(source, LoginHelper.getUserId());
return R.ok("解绑成功");
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.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 org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.system.model.query.UserQuery;
import top.continew.admin.system.model.req.UserPasswordResetReq;
import top.continew.admin.system.model.req.UserReq;
import top.continew.admin.system.model.req.UserRoleUpdateReq;
import top.continew.admin.system.model.resp.UserDetailResp;
import top.continew.admin.system.model.resp.UserResp;
import top.continew.admin.system.service.UserService;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.web.model.R;
/**
* 用户管理 API
*
* @author Charles7c
* @since 2023/2/20 21:00
*/
@Tag(name = "用户管理 API")
@Validated
@RestController
@CrudRequestMapping(value = "/system/user", api = {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class UserController extends BaseController<UserService, UserResp, UserDetailResp, UserQuery, UserReq> {
@Override
@SaCheckPermission("system:user:add")
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody UserReq req) {
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 6 到 32 位,可以包含字母、数字、下划线,特殊字符,同时包含字母和数字");
req.setPassword(rawPassword);
Long id = baseService.add(req);
return R.ok("新增成功", id);
}
@Operation(summary = "重置密码", description = "重置用户登录密码")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:user:password:reset")
@PatchMapping("/{id}/password")
public R<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), "密码长度为 6 到 32 位,可以包含字母、数字、下划线,特殊字符,同时包含字母和数字");
req.setNewPassword(rawNewPassword);
baseService.resetPassword(req, id);
return R.ok("重置密码成功");
}
@Operation(summary = "分配角色", description = "为用户新增或移除角色")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@SaCheckPermission("system:user:role:update")
@PatchMapping("/{id}/role")
public R<Void> updateRole(@Validated @RequestBody UserRoleUpdateReq updateReq, @PathVariable Long id) {
baseService.updateRole(updateReq, id);
return R.ok("分配成功");
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.webapi.tool;
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.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.generator.model.entity.FieldConfigDO;
import top.continew.admin.generator.model.entity.GenConfigDO;
import top.continew.admin.generator.model.query.TableQuery;
import top.continew.admin.generator.model.req.GenConfigReq;
import top.continew.admin.generator.model.resp.GeneratePreviewResp;
import top.continew.admin.generator.model.resp.TableResp;
import top.continew.admin.generator.service.GeneratorService;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
import java.sql.SQLException;
import java.util.List;
/**
* 代码生成 API
*
* @author Charles7c
* @since 2023/8/3 22:58
*/
@Tag(name = "代码生成 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/generator")
public class GeneratorController {
private final ProjectProperties projectProperties;
private final GeneratorService generatorService;
@Operation(summary = "分页查询数据表", description = "分页查询数据表")
@SaCheckPermission("tool:generator:list")
@GetMapping("/table")
public R<PageResp<TableResp>> pageTable(TableQuery query, @Validated PageQuery pageQuery) throws SQLException {
return R.ok(generatorService.pageTable(query, pageQuery));
}
@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("tool:generator:list")
@GetMapping("/field/{tableName}")
public R<List<FieldConfigDO>> listFieldConfig(@PathVariable String tableName,
@RequestParam(required = false, defaultValue = "false") Boolean requireSync) {
return R.ok(generatorService.listFieldConfig(tableName, requireSync));
}
@Operation(summary = "查询生成配置信息", description = "查询生成配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@GetMapping("/config/{tableName}")
public R<GenConfigDO> getGenConfig(@PathVariable String tableName) throws SQLException {
return R.ok(generatorService.getGenConfig(tableName));
}
@Operation(summary = "保存配置信息", description = "保存配置信息")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@PostMapping("/config/{tableName}")
public R<Void> saveConfig(@Validated @RequestBody GenConfigReq req, @PathVariable String tableName) {
generatorService.saveConfig(req, tableName);
return R.ok("保存成功");
}
@Operation(summary = "生成预览", description = "预览生成代码")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@GetMapping("/preview/{tableName}")
public R<List<GeneratePreviewResp>> preview(@PathVariable String tableName) {
return R.ok(generatorService.preview(tableName));
}
@Operation(summary = "生成代码", description = "生成代码")
@Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH)
@SaCheckPermission("tool:generator:list")
@PostMapping("/{tableNames}")
public void generate(@PathVariable List<String> tableNames,
HttpServletRequest request,
HttpServletResponse response) {
generatorService.generate(tableNames, request, response);
}
}