mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-08 08:01:16 +08:00
Compare commits
15 Commits
codex/add-
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfaa4cd094 | ||
|
|
fc414794ff | ||
|
|
d8264956c3 | ||
|
|
effa7f25ca | ||
|
|
9b19fae69a | ||
|
|
ec04f64ce1 | ||
|
|
50bea76c0e | ||
|
|
05522fcdc7 | ||
|
|
3820eaa774 | ||
|
|
7effaf920a | ||
|
|
e40a6a3ca9 | ||
|
|
7c9475cfe2 | ||
|
|
17929dd95d | ||
|
|
f478b55538 | ||
|
|
43fa408f46 |
9
.github/workflows/deploy-docs.yml
vendored
9
.github/workflows/deploy-docs.yml
vendored
@@ -1,7 +1,11 @@
|
|||||||
name: Deploy Documentation
|
name: Deploy Documentation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
build-id:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -16,6 +20,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Log build
|
||||||
|
run: echo "Running documentation deployment from build ${{ inputs.build-id }}"
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
.github/workflows/deploy-staging.yml
vendored
11
.github/workflows/deploy-staging.yml
vendored
@@ -5,6 +5,9 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -21,3 +24,11 @@ jobs:
|
|||||||
key: ${{ secrets.SSH_KEY }}
|
key: ${{ secrets.SSH_KEY }}
|
||||||
script: bash /opt/openisle/deploy-staging.sh
|
script: bash /opt/openisle/deploy-staging.sh
|
||||||
|
|
||||||
|
deploy-docs:
|
||||||
|
needs: build-and-deploy
|
||||||
|
if: ${{ success() }}
|
||||||
|
uses: ./.github/workflows/deploy-docs.yml
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
build-id: ${{ github.run_id }}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ public class CachingConfig {
|
|||||||
public static final String CATEGORY_CACHE_NAME="openisle_categories";
|
public static final String CATEGORY_CACHE_NAME="openisle_categories";
|
||||||
// 在线人数缓存名
|
// 在线人数缓存名
|
||||||
public static final String ONLINE_CACHE_NAME="openisle_online";
|
public static final String ONLINE_CACHE_NAME="openisle_online";
|
||||||
|
// 注册验证码
|
||||||
|
public static final String VERIFY_CACHE_NAME="openisle_verify";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义Redis的序列化器
|
* 自定义Redis的序列化器
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
package com.openisle.controller;
|
package com.openisle.controller;
|
||||||
|
|
||||||
|
import com.openisle.config.CachingConfig;
|
||||||
import com.openisle.dto.*;
|
import com.openisle.dto.*;
|
||||||
import com.openisle.exception.FieldException;
|
import com.openisle.exception.FieldException;
|
||||||
import com.openisle.model.RegisterMode;
|
import com.openisle.model.RegisterMode;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.repository.UserRepository;
|
import com.openisle.repository.UserRepository;
|
||||||
import com.openisle.service.*;
|
import com.openisle.service.*;
|
||||||
|
import com.openisle.util.VerifyType;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
@@ -56,7 +60,8 @@ public class AuthController {
|
|||||||
User user = userService.registerWithInvite(
|
User user = userService.registerWithInvite(
|
||||||
req.getUsername(), req.getEmail(), req.getPassword());
|
req.getUsername(), req.getEmail(), req.getPassword());
|
||||||
inviteService.consume(req.getInviteToken(), user.getUsername());
|
inviteService.consume(req.getInviteToken(), user.getUsername());
|
||||||
emailService.sendEmail(user.getEmail(), "在网站填写验证码以验证", "您的验证码是 " + user.getVerificationCode());
|
// 发送确认邮件
|
||||||
|
userService.sendVerifyMail(user, VerifyType.REGISTER);
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"token", jwtService.generateToken(user.getUsername()),
|
"token", jwtService.generateToken(user.getUsername()),
|
||||||
"reason_code", "INVITE_APPROVED"
|
"reason_code", "INVITE_APPROVED"
|
||||||
@@ -70,7 +75,8 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
User user = userService.register(
|
User user = userService.register(
|
||||||
req.getUsername(), req.getEmail(), req.getPassword(), "", registerModeService.getRegisterMode());
|
req.getUsername(), req.getEmail(), req.getPassword(), "", registerModeService.getRegisterMode());
|
||||||
emailService.sendEmail(user.getEmail(), "在网站填写验证码以验证", "您的验证码是 " + user.getVerificationCode());
|
// 发送确认邮件
|
||||||
|
userService.sendVerifyMail(user, VerifyType.REGISTER);
|
||||||
if (!user.isApproved()) {
|
if (!user.isApproved()) {
|
||||||
notificationService.createRegisterRequestNotifications(user, user.getRegisterReason());
|
notificationService.createRegisterRequestNotifications(user, user.getRegisterReason());
|
||||||
}
|
}
|
||||||
@@ -79,13 +85,12 @@ public class AuthController {
|
|||||||
|
|
||||||
@PostMapping("/verify")
|
@PostMapping("/verify")
|
||||||
public ResponseEntity<?> verify(@RequestBody VerifyRequest req) {
|
public ResponseEntity<?> verify(@RequestBody VerifyRequest req) {
|
||||||
boolean ok = userService.verifyCode(req.getUsername(), req.getCode());
|
Optional<User> userOpt = userService.findByUsername(req.getUsername());
|
||||||
|
if (userOpt.isEmpty()) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Invalid credentials"));
|
||||||
|
}
|
||||||
|
boolean ok = userService.verifyCode(userOpt.get(), req.getCode(), VerifyType.REGISTER);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
Optional<User> userOpt = userService.findByUsername(req.getUsername());
|
|
||||||
if (userOpt.isEmpty()) {
|
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "Invalid credentials"));
|
|
||||||
}
|
|
||||||
|
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
if (user.isApproved()) {
|
if (user.isApproved()) {
|
||||||
@@ -122,7 +127,7 @@ public class AuthController {
|
|||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (!user.isVerified()) {
|
if (!user.isVerified()) {
|
||||||
user = userService.register(user.getUsername(), user.getEmail(), user.getPassword(), user.getRegisterReason(), registerModeService.getRegisterMode());
|
user = userService.register(user.getUsername(), user.getEmail(), user.getPassword(), user.getRegisterReason(), registerModeService.getRegisterMode());
|
||||||
emailService.sendEmail(user.getEmail(), "在网站填写验证码以验证", "您的验证码是 " + user.getVerificationCode());
|
userService.sendVerifyMail(user, VerifyType.REGISTER);
|
||||||
return ResponseEntity.badRequest().body(Map.of(
|
return ResponseEntity.badRequest().body(Map.of(
|
||||||
"error", "User not verified",
|
"error", "User not verified",
|
||||||
"reason_code", "NOT_VERIFIED",
|
"reason_code", "NOT_VERIFIED",
|
||||||
@@ -417,14 +422,17 @@ public class AuthController {
|
|||||||
if (userOpt.isEmpty()) {
|
if (userOpt.isEmpty()) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "User not found"));
|
return ResponseEntity.badRequest().body(Map.of("error", "User not found"));
|
||||||
}
|
}
|
||||||
String code = userService.generatePasswordResetCode(req.getEmail());
|
userService.sendVerifyMail(userOpt.get(), VerifyType.RESET_PASSWORD);
|
||||||
emailService.sendEmail(req.getEmail(), "请填写验证码以重置密码", "您的验证码是" + code);
|
|
||||||
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/forgot/verify")
|
@PostMapping("/forgot/verify")
|
||||||
public ResponseEntity<?> verifyReset(@RequestBody VerifyForgotRequest req) {
|
public ResponseEntity<?> verifyReset(@RequestBody VerifyForgotRequest req) {
|
||||||
boolean ok = userService.verifyPasswordResetCode(req.getEmail(), req.getCode());
|
Optional<User> userOpt = userService.findByEmail(req.getEmail());
|
||||||
|
if (userOpt.isEmpty()) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "User not found"));
|
||||||
|
}
|
||||||
|
boolean ok = userService.verifyCode(userOpt.get(), req.getCode(), VerifyType.RESET_PASSWORD);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
String username = userService.findByEmail(req.getEmail()).get().getUsername();
|
String username = userService.findByEmail(req.getEmail()).get().getUsername();
|
||||||
return ResponseEntity.ok(Map.of("token", jwtService.generateResetToken(username)));
|
return ResponseEntity.ok(Map.of("token", jwtService.generateResetToken(username)));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.openisle.service;
|
package com.openisle.service;
|
||||||
|
|
||||||
|
import com.openisle.config.CachingConfig;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.model.Role;
|
import com.openisle.model.Role;
|
||||||
import com.openisle.service.PasswordValidator;
|
import com.openisle.service.PasswordValidator;
|
||||||
@@ -7,13 +8,18 @@ import com.openisle.service.UsernameValidator;
|
|||||||
import com.openisle.service.AvatarGenerator;
|
import com.openisle.service.AvatarGenerator;
|
||||||
import com.openisle.exception.FieldException;
|
import com.openisle.exception.FieldException;
|
||||||
import com.openisle.repository.UserRepository;
|
import com.openisle.repository.UserRepository;
|
||||||
|
import com.openisle.util.VerifyType;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -25,6 +31,10 @@ public class UserService {
|
|||||||
private final ImageUploader imageUploader;
|
private final ImageUploader imageUploader;
|
||||||
private final AvatarGenerator avatarGenerator;
|
private final AvatarGenerator avatarGenerator;
|
||||||
|
|
||||||
|
private final RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
private final EmailSender emailService;
|
||||||
|
|
||||||
public User register(String username, String email, String password, String reason, com.openisle.model.RegisterMode mode) {
|
public User register(String username, String email, String password, String reason, com.openisle.model.RegisterMode mode) {
|
||||||
usernameValidator.validate(username);
|
usernameValidator.validate(username);
|
||||||
passwordValidator.validate(password);
|
passwordValidator.validate(password);
|
||||||
@@ -38,7 +48,7 @@ public class UserService {
|
|||||||
// 未验证 → 允许“重注册”:覆盖必要字段并重新发验证码
|
// 未验证 → 允许“重注册”:覆盖必要字段并重新发验证码
|
||||||
u.setEmail(email); // 若不允许改邮箱可去掉
|
u.setEmail(email); // 若不允许改邮箱可去掉
|
||||||
u.setPassword(passwordEncoder.encode(password));
|
u.setPassword(passwordEncoder.encode(password));
|
||||||
u.setVerificationCode(genCode());
|
// u.setVerificationCode(genCode());
|
||||||
u.setRegisterReason(reason);
|
u.setRegisterReason(reason);
|
||||||
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
return userRepository.save(u);
|
return userRepository.save(u);
|
||||||
@@ -54,7 +64,7 @@ public class UserService {
|
|||||||
// 未验证 → 允许“重注册”
|
// 未验证 → 允许“重注册”
|
||||||
u.setUsername(username); // 若不允许改用户名可去掉
|
u.setUsername(username); // 若不允许改用户名可去掉
|
||||||
u.setPassword(passwordEncoder.encode(password));
|
u.setPassword(passwordEncoder.encode(password));
|
||||||
u.setVerificationCode(genCode());
|
// u.setVerificationCode(genCode());
|
||||||
u.setRegisterReason(reason);
|
u.setRegisterReason(reason);
|
||||||
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
return userRepository.save(u);
|
return userRepository.save(u);
|
||||||
@@ -67,7 +77,7 @@ public class UserService {
|
|||||||
user.setPassword(passwordEncoder.encode(password));
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
user.setRole(Role.USER);
|
user.setRole(Role.USER);
|
||||||
user.setVerified(false);
|
user.setVerified(false);
|
||||||
user.setVerificationCode(genCode());
|
// user.setVerificationCode(genCode());
|
||||||
user.setAvatar(avatarGenerator.generate(username));
|
user.setAvatar(avatarGenerator.generate(username));
|
||||||
user.setRegisterReason(reason);
|
user.setRegisterReason(reason);
|
||||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
@@ -77,7 +87,7 @@ public class UserService {
|
|||||||
public User registerWithInvite(String username, String email, String password) {
|
public User registerWithInvite(String username, String email, String password) {
|
||||||
User user = register(username, email, password, "", com.openisle.model.RegisterMode.DIRECT);
|
User user = register(username, email, password, "", com.openisle.model.RegisterMode.DIRECT);
|
||||||
user.setVerified(true);
|
user.setVerified(true);
|
||||||
user.setVerificationCode(genCode());
|
// user.setVerificationCode(genCode());
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,16 +95,58 @@ public class UserService {
|
|||||||
return String.format("%06d", new Random().nextInt(1000000));
|
return String.format("%06d", new Random().nextInt(1000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verifyCode(String username, String code) {
|
/**
|
||||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
* 将验证码存入缓存,并发送邮件
|
||||||
if (userOpt.isPresent() && code.equals(userOpt.get().getVerificationCode())) {
|
* @param user
|
||||||
User user = userOpt.get();
|
*/
|
||||||
user.setVerified(true);
|
public void sendVerifyMail(User user, VerifyType verifyType){
|
||||||
user.setVerificationCode(null);
|
//缓存验证码
|
||||||
userRepository.save(user);
|
String code = genCode();
|
||||||
return true;
|
String key;
|
||||||
|
String subject;
|
||||||
|
String content = "您的验证码是:" + code;
|
||||||
|
// 注册类型
|
||||||
|
if(verifyType.equals(VerifyType.REGISTER)){
|
||||||
|
key = CachingConfig.VERIFY_CACHE_NAME + ":register:code:" + user.getUsername();
|
||||||
|
subject = "在网站填写验证码以验证(有效期为5分钟)";
|
||||||
|
}else {
|
||||||
|
// 重置密码
|
||||||
|
key = CachingConfig.VERIFY_CACHE_NAME + ":reset_password:code:" + user.getUsername();
|
||||||
|
subject = "请填写验证码以重置密码(有效期为5分钟)";
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);// 五分钟后验证码过期
|
||||||
|
emailService.sendEmail(user.getEmail(), subject, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证code是否正确
|
||||||
|
* @param user
|
||||||
|
* @param code
|
||||||
|
* @param verifyType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean verifyCode(User user, String code, VerifyType verifyType) {
|
||||||
|
// 生成key
|
||||||
|
String key1 = VerifyType.REGISTER.equals(verifyType)?":register:code:":":reset_password:code:";
|
||||||
|
String key = CachingConfig.VERIFY_CACHE_NAME + key1 + user.getUsername();
|
||||||
|
// 这里不能使用getAndDelete,需要6.x版本
|
||||||
|
String cachedCode = (String)redisTemplate.opsForValue().get(key);
|
||||||
|
// 如果校验code过期或者不存在
|
||||||
|
// 或者校验code不一致
|
||||||
|
if(Objects.isNull(cachedCode)
|
||||||
|
|| !cachedCode.equals(code)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 注册模式需要设置已经确认
|
||||||
|
if(VerifyType.REGISTER.equals(verifyType)){
|
||||||
|
user.setVerified(true);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
// 走到这里说明验证成功删除验证码
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<User> authenticate(String username, String password) {
|
public Optional<User> authenticate(String username, String password) {
|
||||||
@@ -165,26 +217,6 @@ public class UserService {
|
|||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generatePasswordResetCode(String email) {
|
|
||||||
User user = userRepository.findByEmail(email)
|
|
||||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
|
||||||
String code = genCode();
|
|
||||||
user.setPasswordResetCode(code);
|
|
||||||
userRepository.save(user);
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean verifyPasswordResetCode(String email, String code) {
|
|
||||||
Optional<User> userOpt = userRepository.findByEmail(email);
|
|
||||||
if (userOpt.isPresent() && code.equals(userOpt.get().getPasswordResetCode())) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
user.setPasswordResetCode(null);
|
|
||||||
userRepository.save(user);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public User updatePassword(String username, String newPassword) {
|
public User updatePassword(String username, String newPassword) {
|
||||||
passwordValidator.validate(newPassword);
|
passwordValidator.validate(newPassword);
|
||||||
User user = userRepository.findByUsername(username)
|
User user = userRepository.findByUsername(username)
|
||||||
|
|||||||
20
backend/src/main/java/com/openisle/util/VerifyType.java
Normal file
20
backend/src/main/java/com/openisle/util/VerifyType.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.openisle.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码类型
|
||||||
|
* @author smallclover
|
||||||
|
* @since 2025-09-08
|
||||||
|
*/
|
||||||
|
public enum VerifyType {
|
||||||
|
REGISTER(1),
|
||||||
|
RESET_PASSWORD(2);
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
VerifyType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,6 +108,7 @@ rabbitmq.sharding.enabled=true
|
|||||||
# see https://springdoc.org/#springdoc-openapi-core-properties
|
# see https://springdoc.org/#springdoc-openapi-core-properties
|
||||||
springdoc.api-docs.path=/api/v3/api-docs
|
springdoc.api-docs.path=/api/v3/api-docs
|
||||||
springdoc.api-docs.enabled=true
|
springdoc.api-docs.enabled=true
|
||||||
|
springdoc.api-docs.server-url=${WEBSITE_URL:https://www.open-isle.com}
|
||||||
springdoc.info.title=OpenIsle
|
springdoc.info.title=OpenIsle
|
||||||
springdoc.info.description=OpenIsle Open API Documentation
|
springdoc.info.description=OpenIsle Open API Documentation
|
||||||
springdoc.info.version=0.0.1
|
springdoc.info.version=0.0.1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.openisle.model.User;
|
|||||||
import com.openisle.service.*;
|
import com.openisle.service.*;
|
||||||
import com.openisle.model.RegisterMode;
|
import com.openisle.model.RegisterMode;
|
||||||
import com.openisle.repository.UserRepository;
|
import com.openisle.repository.UserRepository;
|
||||||
|
import com.openisle.util.VerifyType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -71,7 +72,9 @@ class AuthControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void verifyCodeEndpoint() throws Exception {
|
void verifyCodeEndpoint() throws Exception {
|
||||||
Mockito.when(userService.verifyCode("u", "123")).thenReturn(true);
|
User user = new User();
|
||||||
|
user.setUsername("u");
|
||||||
|
Mockito.when(userService.verifyCode(user, "123", VerifyType.REGISTER)).thenReturn(true);
|
||||||
Mockito.when(jwtService.generateReasonToken("u")).thenReturn("reason_token");
|
Mockito.when(jwtService.generateReasonToken("u")).thenReturn("reason_token");
|
||||||
|
|
||||||
mockMvc.perform(post("/api/auth/verify")
|
mockMvc.perform(post("/api/auth/verify")
|
||||||
|
|||||||
@@ -37,11 +37,12 @@ class PostServiceTest {
|
|||||||
EmailSender emailSender = mock(EmailSender.class);
|
EmailSender emailSender = mock(EmailSender.class);
|
||||||
ApplicationContext context = mock(ApplicationContext.class);
|
ApplicationContext context = mock(ApplicationContext.class);
|
||||||
PointService pointService = mock(PointService.class);
|
PointService pointService = mock(PointService.class);
|
||||||
|
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||||
|
|
||||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||||
when(context.getBean(PostService.class)).thenReturn(service);
|
when(context.getBean(PostService.class)).thenReturn(service);
|
||||||
|
|
||||||
Post post = new Post();
|
Post post = new Post();
|
||||||
@@ -86,11 +87,12 @@ class PostServiceTest {
|
|||||||
EmailSender emailSender = mock(EmailSender.class);
|
EmailSender emailSender = mock(EmailSender.class);
|
||||||
ApplicationContext context = mock(ApplicationContext.class);
|
ApplicationContext context = mock(ApplicationContext.class);
|
||||||
PointService pointService = mock(PointService.class);
|
PointService pointService = mock(PointService.class);
|
||||||
|
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||||
|
|
||||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||||
when(context.getBean(PostService.class)).thenReturn(service);
|
when(context.getBean(PostService.class)).thenReturn(service);
|
||||||
|
|
||||||
Post post = new Post();
|
Post post = new Post();
|
||||||
@@ -141,11 +143,12 @@ class PostServiceTest {
|
|||||||
EmailSender emailSender = mock(EmailSender.class);
|
EmailSender emailSender = mock(EmailSender.class);
|
||||||
ApplicationContext context = mock(ApplicationContext.class);
|
ApplicationContext context = mock(ApplicationContext.class);
|
||||||
PointService pointService = mock(PointService.class);
|
PointService pointService = mock(PointService.class);
|
||||||
|
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||||
|
|
||||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||||
when(context.getBean(PostService.class)).thenReturn(service);
|
when(context.getBean(PostService.class)).thenReturn(service);
|
||||||
|
|
||||||
when(postRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(1L);
|
when(postRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(1L);
|
||||||
@@ -177,11 +180,12 @@ class PostServiceTest {
|
|||||||
EmailSender emailSender = mock(EmailSender.class);
|
EmailSender emailSender = mock(EmailSender.class);
|
||||||
ApplicationContext context = mock(ApplicationContext.class);
|
ApplicationContext context = mock(ApplicationContext.class);
|
||||||
PointService pointService = mock(PointService.class);
|
PointService pointService = mock(PointService.class);
|
||||||
|
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||||
|
|
||||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||||
when(context.getBean(PostService.class)).thenReturn(service);
|
when(context.getBean(PostService.class)).thenReturn(service);
|
||||||
|
|
||||||
User author = new User();
|
User author = new User();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
--background-color: white;
|
--background-color: white;
|
||||||
--background-color-blur: rgba(255, 255, 255, 0.57);
|
--background-color-blur: rgba(255, 255, 255, 0.57);
|
||||||
--menu-border-color: lightgray;
|
--menu-border-color: lightgray;
|
||||||
--normal-border-color: lightgray;
|
--normal-border-color: rgba(211, 211, 211, 0.63);
|
||||||
--menu-selected-background-color: rgba(88, 241, 255, 0.166);
|
--menu-selected-background-color: rgba(88, 241, 255, 0.166);
|
||||||
--normal-light-background-color: rgba(242, 242, 242, 0.884);
|
--normal-light-background-color: rgba(242, 242, 242, 0.884);
|
||||||
--menu-selected-background-color-hover: rgba(242, 242, 242, 0.884);
|
--menu-selected-background-color-hover: rgba(242, 242, 242, 0.884);
|
||||||
@@ -348,6 +348,22 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Adjust diff2html layout on mobile */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.content-diff .d2h-wrapper,
|
||||||
|
.content-diff .d2h-code-line,
|
||||||
|
.content-diff .d2h-code-side-line,
|
||||||
|
.content-diff .d2h-code-line-ctn,
|
||||||
|
.content-diff .d2h-code-side-line-ctn,
|
||||||
|
.content-diff .d2h-file-header {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-diff .d2h-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Transition API */
|
/* Transition API */
|
||||||
::view-transition-old(root),
|
::view-transition-old(root),
|
||||||
::view-transition-new(root) {
|
::view-transition-new(root) {
|
||||||
|
|||||||
@@ -11,18 +11,18 @@
|
|||||||
<span v-if="log.username" class="change-log-user">{{ log.username }}</span>
|
<span v-if="log.username" class="change-log-user">{{ log.username }}</span>
|
||||||
<span v-if="log.type === 'CONTENT'" class="change-log-content">变更了文章内容</span>
|
<span v-if="log.type === 'CONTENT'" class="change-log-content">变更了文章内容</span>
|
||||||
<span v-else-if="log.type === 'TITLE'" class="change-log-content">变更了文章标题</span>
|
<span v-else-if="log.type === 'TITLE'" class="change-log-content">变更了文章标题</span>
|
||||||
<span v-else-if="log.type === 'CATEGORY'" class="change-log-content change-log-category">
|
<template v-else-if="log.type === 'CATEGORY'">
|
||||||
<div class="change-log-category-text">变更了文章分类, 从</div>
|
<div class="change-log-category-text">变更了文章分类, 从</div>
|
||||||
<ArticleCategory :category="log.oldCategory" />
|
<ArticleCategory :category="log.oldCategory" />
|
||||||
<div class="change-log-category-text">修改为</div>
|
<div class="change-log-category-text">修改为</div>
|
||||||
<ArticleCategory :category="log.newCategory" />
|
<ArticleCategory :category="log.newCategory" />
|
||||||
</span>
|
</template>
|
||||||
<span v-else-if="log.type === 'TAG'" class="change-log-content change-log-category">
|
<template v-else-if="log.type === 'TAG'">
|
||||||
<div class="change-log-category-text">变更了文章标签, 从</div>
|
<div class="change-log-category-text">变更了文章标签, 从</div>
|
||||||
<ArticleTags :tags="log.oldTags" />
|
<ArticleTags :tags="log.oldTags" />
|
||||||
<div class="change-log-category-text">修改为</div>
|
<div class="change-log-category-text">修改为</div>
|
||||||
<ArticleTags :tags="log.newTags" />
|
<ArticleTags :tags="log.newTags" />
|
||||||
</span>
|
</template>
|
||||||
<span v-else-if="log.type === 'CLOSED'" class="change-log-content">
|
<span v-else-if="log.type === 'CLOSED'" class="change-log-content">
|
||||||
<template v-if="log.newClosed">关闭了文章</template>
|
<template v-if="log.newClosed">关闭了文章</template>
|
||||||
<template v-else>重新打开了文章</template>
|
<template v-else>重新打开了文章</template>
|
||||||
@@ -68,7 +68,6 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const diffHtml = computed(() => {
|
const diffHtml = computed(() => {
|
||||||
const isMobile = useIsMobile()
|
|
||||||
// Track theme changes
|
// Track theme changes
|
||||||
const isDark = import.meta.client && document.documentElement.dataset.theme === 'dark'
|
const isDark = import.meta.client && document.documentElement.dataset.theme === 'dark'
|
||||||
themeState.mode
|
themeState.mode
|
||||||
@@ -83,7 +82,6 @@ const diffHtml = computed(() => {
|
|||||||
showFiles: false,
|
showFiles: false,
|
||||||
matching: 'lines',
|
matching: 'lines',
|
||||||
drawFileList: false,
|
drawFileList: false,
|
||||||
outputFormat: isMobile.value ? 'line-by-line' : 'side-by-side',
|
|
||||||
colorScheme,
|
colorScheme,
|
||||||
})
|
})
|
||||||
} else if (props.log.type === 'TITLE') {
|
} else if (props.log.type === 'TITLE') {
|
||||||
@@ -95,7 +93,6 @@ const diffHtml = computed(() => {
|
|||||||
showFiles: false,
|
showFiles: false,
|
||||||
matching: 'lines',
|
matching: 'lines',
|
||||||
drawFileList: false,
|
drawFileList: false,
|
||||||
outputFormat: isMobile.value ? 'line-by-line' : 'side-by-side',
|
|
||||||
colorScheme,
|
colorScheme,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -110,9 +107,12 @@ const diffHtml = computed(() => {
|
|||||||
/* padding-top: 5px; */
|
/* padding-top: 5px; */
|
||||||
/* padding-bottom: 30px; */
|
/* padding-bottom: 30px; */
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid var(--normal-border-color);
|
||||||
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
.change-log-text {
|
.change-log-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.change-log-user {
|
.change-log-user {
|
||||||
@@ -146,5 +146,6 @@ const diffHtml = computed(() => {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1163,6 +1163,7 @@ onMounted(async () => {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-content-container {
|
.info-content-container {
|
||||||
@@ -1218,7 +1219,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.post-time {
|
.post-time {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1284,10 +1285,6 @@ onMounted(async () => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-time {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-content-text {
|
.info-content-text {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user