diff --git a/code/link56/link56-backend/.gitignore b/code/link56/link56-backend/.gitignore new file mode 100644 index 00000000..480bdf52 --- /dev/null +++ b/code/link56/link56-backend/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/code/link56/link56-backend/.idea/.gitignore b/code/link56/link56-backend/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/code/link56/link56-backend/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/code/link56/link56-backend/.idea/codeStyles/codeStyleConfig.xml b/code/link56/link56-backend/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/code/link56/link56-backend/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/.idea/encodings.xml b/code/link56/link56-backend/.idea/encodings.xml new file mode 100644 index 00000000..aa00ffab --- /dev/null +++ b/code/link56/link56-backend/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/.idea/misc.xml b/code/link56/link56-backend/.idea/misc.xml new file mode 100644 index 00000000..132404bc --- /dev/null +++ b/code/link56/link56-backend/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/.idea/vcs.xml b/code/link56/link56-backend/.idea/vcs.xml new file mode 100644 index 00000000..c2365ab1 --- /dev/null +++ b/code/link56/link56-backend/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/pom.xml b/code/link56/link56-backend/pom.xml new file mode 100644 index 00000000..681093a2 --- /dev/null +++ b/code/link56/link56-backend/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + cn.link56 + link56-backend + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + + 8 + 8 + UTF-8 + + 3.10.3 + 1.3.1 + 1.2.23 + 3.5.10.1 + 3.12.0 + + + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-mail + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + net.sourceforge.jtds + jtds + ${jtds.version} + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatisplus.version} + + + + com.baomidou + mybatis-plus-jsqlparser-4.9 + ${mybatisplus.version} + + + + com.auth0 + java-jwt + ${jwt.version} + + + + org.apache.commons + commons-lang3 + ${apachecommons.version} + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public/ + + true + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public/ + + true + + + false + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/java/cn/link56/Link56Application.java b/code/link56/link56-backend/src/main/java/cn/link56/Link56Application.java new file mode 100644 index 00000000..50ab60b8 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/Link56Application.java @@ -0,0 +1,13 @@ +package cn.link56; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; + +@SpringBootApplication +@EnableAsync +public class Link56Application { + public static void main(String[] args) { + SpringApplication.run(Link56Application.class,args); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/controller/AuthCodeController.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/controller/AuthCodeController.java new file mode 100644 index 00000000..f52690ea --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/controller/AuthCodeController.java @@ -0,0 +1,41 @@ +package cn.link56.auth.code.controller; + +import cn.link56.auth.code.model.AuthCodeChannel; +import cn.link56.auth.code.model.AuthCodePurpose; +import cn.link56.auth.code.model.dto.AuthCodeSendDTO; +import cn.link56.auth.code.model.dto.AuthCodeVerifyDTO; +import cn.link56.auth.code.service.VerificationCodeService; +import cn.link56.common.Result; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/auth/code") +public class AuthCodeController { + @Resource + private VerificationCodeService verificationCodeService; + + @PostMapping("/send") + public Result send(@RequestBody AuthCodeSendDTO authCodeSendDTO) { + AuthCodePurpose purpose = authCodeSendDTO.getPurpose(); + AuthCodeChannel channel = authCodeSendDTO.getChannel(); + String target = authCodeSendDTO.getTarget(); + + boolean flag = verificationCodeService.sendCode(purpose, channel, target); + + return flag ? Result.success() : Result.error(); + } + + @PostMapping("/verify") + public Result verify(@RequestBody AuthCodeVerifyDTO authCodeVerifyDTO) { + AuthCodePurpose purpose = authCodeVerifyDTO.getPurpose(); + AuthCodeChannel channel = authCodeVerifyDTO.getChannel(); + String target = authCodeVerifyDTO.getTarget(); + String code = authCodeVerifyDTO.getCode(); + + boolean flag = verificationCodeService.verifyCode(purpose, channel, target, code); + + return flag ? Result.success() : Result.error(); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCode.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCode.java new file mode 100644 index 00000000..f169623d --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCode.java @@ -0,0 +1,16 @@ +package cn.link56.auth.code.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthCode { + private String code; + private Instant expireAt; + private Instant lastSentAt; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCodeChannel.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCodeChannel.java new file mode 100644 index 00000000..d1bd3008 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCodeChannel.java @@ -0,0 +1,6 @@ +package cn.link56.auth.code.model; + +public enum AuthCodeChannel { + EMAIL, + SMS +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCodePurpose.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCodePurpose.java new file mode 100644 index 00000000..d28262e1 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/AuthCodePurpose.java @@ -0,0 +1,10 @@ +package cn.link56.auth.code.model; + +/** + * 验证码用途 / 业务场景 + */ +public enum AuthCodePurpose { + REGISTER, // 注册 + LOGIN, // 登录 + RESET_PASSWORD // 找回密码 +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/dto/AuthCodeSendDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/dto/AuthCodeSendDTO.java new file mode 100644 index 00000000..dc99c84b --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/dto/AuthCodeSendDTO.java @@ -0,0 +1,21 @@ +package cn.link56.auth.code.model.dto; + +import cn.link56.auth.code.model.AuthCodeChannel; +import cn.link56.auth.code.model.AuthCodePurpose; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthCodeSendDTO { + private AuthCodePurpose purpose; + + private AuthCodeChannel channel; + + /** + * 邮箱 / 手机号 + */ + private String target; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/dto/AuthCodeVerifyDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/dto/AuthCodeVerifyDTO.java new file mode 100644 index 00000000..252d1883 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/model/dto/AuthCodeVerifyDTO.java @@ -0,0 +1,23 @@ +package cn.link56.auth.code.model.dto; + +import cn.link56.auth.code.model.AuthCodeChannel; +import cn.link56.auth.code.model.AuthCodePurpose; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthCodeVerifyDTO { + private AuthCodePurpose purpose; + + private AuthCodeChannel channel; + + /** + * 邮箱 / 手机号 + */ + private String target; + + private String code; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/sender/EmailAuthCodeSender.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/sender/EmailAuthCodeSender.java new file mode 100644 index 00000000..049fa8bf --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/sender/EmailAuthCodeSender.java @@ -0,0 +1,37 @@ +package cn.link56.auth.code.sender; + +import cn.link56.auth.code.model.AuthCodePurpose; +import cn.link56.common.utils.EmailUtils; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +@Component +public class EmailAuthCodeSender { + @Resource + private EmailUtils emailUtils; + + public boolean send(String email, String code, AuthCodePurpose purpose) { + String subject = ""; + String templateHtml = ""; + + Map payload = new HashMap<>(); + + if (purpose.equals(AuthCodePurpose.LOGIN)){ + subject = "登录"; + }else if(purpose.equals(AuthCodePurpose.REGISTER)){ + subject = "注册"; + templateHtml = "Register.html"; + }else if(purpose.equals(AuthCodePurpose.RESET_PASSWORD)){ + subject = "找回密码"; + templateHtml = "ForgetPassword.html"; + } + + payload.put("Code",code); + + return emailUtils.sendSync(templateHtml,subject,payload,email); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/sender/SmsAuthCodeSender.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/sender/SmsAuthCodeSender.java new file mode 100644 index 00000000..56c7e2f3 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/sender/SmsAuthCodeSender.java @@ -0,0 +1,11 @@ +package cn.link56.auth.code.sender; + +import cn.link56.auth.code.model.AuthCodePurpose; +import org.springframework.stereotype.Component; + +@Component +public class SmsAuthCodeSender { + public boolean send(String email, String code, AuthCodePurpose purpose) { + return true; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/service/VerificationCodeService.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/service/VerificationCodeService.java new file mode 100644 index 00000000..82e78f2f --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/service/VerificationCodeService.java @@ -0,0 +1,55 @@ +package cn.link56.auth.code.service; + +import cn.link56.auth.code.model.AuthCodeChannel; +import cn.link56.auth.code.model.AuthCode; +import cn.link56.auth.code.model.AuthCodePurpose; +import cn.link56.auth.code.sender.EmailAuthCodeSender; +import cn.link56.auth.code.sender.SmsAuthCodeSender; +import cn.link56.auth.code.store.AuthCodeStore; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service +public class VerificationCodeService { + @Resource + private AuthCodeStore codeStore; + + @Resource + private EmailAuthCodeSender emailAuthCodeSender; + + @Resource + private SmsAuthCodeSender smsAuthCodeSender; + + public boolean sendCode( + AuthCodePurpose purpose, + AuthCodeChannel channel, + String target + ) { + AuthCode code = codeStore.generate(purpose, channel, target); + + boolean success; + + switch (channel) { + case EMAIL: + success = emailAuthCodeSender.send(target, code.getCode(), purpose); + break; + case SMS: + success = smsAuthCodeSender.send(target, code.getCode(), purpose); + break; + default: + throw new IllegalArgumentException("不支持类型: " + channel); + } + + return success; + } + + public boolean verifyCode( + AuthCodePurpose purpose, + AuthCodeChannel channel, + String target, + String inputCode + ) { + return codeStore.verify(purpose, channel, target, inputCode); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/code/store/AuthCodeStore.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/store/AuthCodeStore.java new file mode 100644 index 00000000..5964b8a1 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/code/store/AuthCodeStore.java @@ -0,0 +1,81 @@ +package cn.link56.auth.code.store; + +import cn.link56.auth.code.model.AuthCodeChannel; +import cn.link56.auth.code.model.AuthCode; +import cn.link56.auth.code.model.AuthCodePurpose; +import cn.link56.common.exception.OperationTooFrequentException; +import org.springframework.stereotype.Component; + +import javax.naming.OperationNotSupportedException; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class AuthCodeStore { + private final Map store = new ConcurrentHashMap<>(); + + private static final long EXPIRE_SECONDS = 600; + private static final long MIN_INTERVAL_SECONDS = 60; + + public AuthCode generate( + AuthCodePurpose purpose, + AuthCodeChannel channel, + String target + ) { + String key = buildKey(purpose, channel, target); + Instant now = Instant.now(); + + AuthCode existing = store.get(key); + if (existing != null && + existing.getLastSentAt().plusSeconds(MIN_INTERVAL_SECONDS).isAfter(now)) { + throw new OperationTooFrequentException("验证码发送过于频繁,请稍后再试"); + } + + AuthCode code = new AuthCode( + randomCode(6), + now.plusSeconds(EXPIRE_SECONDS), + now + ); + + store.put(key, code); + return code; + } + + public boolean verify( + AuthCodePurpose purpose, + AuthCodeChannel channel, + String target, + String inputCode + ) { + String key = buildKey(purpose, channel, target); + AuthCode code = store.get(key); + + if (code == null || Instant.now().isAfter(code.getExpireAt())) { + store.remove(key); + return false; + } + + boolean matched = code.getCode().equals(inputCode); + if (matched) { + store.remove(key); // 一次性使用 + } + return matched; + } + + private String buildKey( + AuthCodePurpose purpose, + AuthCodeChannel channel, + String target + ) { + return purpose.name() + ":" + channel.name() + ":" + target; + } + + private String randomCode(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append((int) (Math.random() * 10)); + } + return sb.toString(); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/BCompany.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/BCompany.java new file mode 100644 index 00000000..f8924c54 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/BCompany.java @@ -0,0 +1,50 @@ +package cn.link56.auth.identity.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName("b_company") +@Accessors(chain = true) +public class BCompany { + @TableId(value = "b_id") + private Long id; + @TableField("b_name") + private String name; + @TableField("b_email") + private String email; + @TableField("b_phone") + private String phone; + @TableField("b_address") + private String address; + @TableField("b_industry") + private String industry; + @TableField("b_logo") + private String logo; + @TableField("b_profile") + private String profile; + @TableField("b_description") + private String description; + @TableField("b_create_by") + private Long createBy; + @TableField("b_create_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + @TableField("b_update_by") + private Long updateBy; + @TableField("b_update_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + @TableField("b_status") + private Integer status; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/BCompanyUser.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/BCompanyUser.java new file mode 100644 index 00000000..2e362d3b --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/BCompanyUser.java @@ -0,0 +1,44 @@ +package cn.link56.auth.identity.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName("b_company_user") +@Accessors(chain = true) +public class BCompanyUser { + @TableId(value = "b_id") + private Long id; + @TableId(value = "b_company_id") + private Long companyId; + @TableField("b_email") + private String email; + @TableField("b_password") + private String password; + @TableField("b_phone") + private String phone; + @TableField("b_role") + private Integer role; + @TableField("b_position") + private String position; + @TableField("b_create_by") + private Long createBy; + @TableField("b_create_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + @TableField("b_update_by") + private Long updateBy; + @TableField("b_update_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/dto/AuthRegisterDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/dto/AuthRegisterDTO.java new file mode 100644 index 00000000..1b6b217d --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/identity/model/dto/AuthRegisterDTO.java @@ -0,0 +1,46 @@ +package cn.link56.auth.identity.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthRegisterDTO { + private Company company; + private Admin admin; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Company { + @NotBlank(message = "公司名称不能为空") + private String name; + @NotBlank(message = "所属行业不能为空") + private String industry; + @NotBlank(message = "公司地址不能为空") + private String address; + private String phone; + private String email; + private String profile; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Admin { + @NotBlank(message = "管理员名称不能为空") + private String name; + private String phone; + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式有误") + private String email; + @NotBlank(message = "密码不能为空") + private String password; + private String position; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/token/model/AuthToken.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/token/model/AuthToken.java new file mode 100644 index 00000000..4a110db1 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/token/model/AuthToken.java @@ -0,0 +1,16 @@ +package cn.link56.auth.token.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@AllArgsConstructor +@NoArgsConstructor +@Data +public class AuthToken { + private String token; // 实际的 token 字符串 + private Instant expireAt; // 过期时间 + private TokenPayload payload; // 携带用户信息 +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/token/model/TokenPayload.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/token/model/TokenPayload.java new file mode 100644 index 00000000..7e924022 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/token/model/TokenPayload.java @@ -0,0 +1,13 @@ +package cn.link56.auth.token.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TokenPayload { + private Long userId; // 用户唯一标识 + private Long companyId; // 用户所属公司ID +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/auth/token/service/AuthTokenService.java b/code/link56/link56-backend/src/main/java/cn/link56/auth/token/service/AuthTokenService.java new file mode 100644 index 00000000..892dfe2c --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/auth/token/service/AuthTokenService.java @@ -0,0 +1,83 @@ +package cn.link56.auth.token.service; + +import cn.link56.auth.token.model.AuthToken; +import cn.link56.auth.token.model.TokenPayload; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Date; + +@Service +public class AuthTokenService { + private static final String SECRET_KEY = "dasdasdad661d65a1s65da65ds!"; + private static final long TOKEN_EXPIRE_SECONDS = 30L * 24 * 3600; // 30天 + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); + + /** + * 生成 JWT Token + */ + public AuthToken generateToken(TokenPayload payload) { + try { + String payloadJson = objectMapper.writeValueAsString(payload); + Instant now = Instant.now(); + Instant expireAt = now.plusSeconds(TOKEN_EXPIRE_SECONDS); + + String token = JWT.create() + .withSubject(payload.getUserId().toString()) + .withClaim("payload", payloadJson) + .withIssuedAt(Date.from(now)) + .withExpiresAt(Date.from(expireAt)) + .sign(algorithm); + + return new AuthToken(token, expireAt, payload); + } catch (Exception e) { + throw new RuntimeException("Failed to generate token", e); + } + } + + /** + * 验证 Token 是否有效 + */ + public boolean validateToken(String token) { + try { + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(token); + return true; + } catch (JWTVerificationException e) { + return false; + } + } + + /** + * 解析 Token Payload + */ + public TokenPayload parsePayload(String token) { + try { + DecodedJWT decoded = JWT.require(algorithm).build().verify(token); + String payloadJson = decoded.getClaim("payload").asString(); + return objectMapper.readValue(payloadJson, TokenPayload.class); + } catch (Exception e) { + return null; + } + } + + /** + * 获取 Token 过期时间 + */ + public Instant getExpireAt(String token) { + try { + DecodedJWT decoded = JWT.require(algorithm).build().verify(token); + return decoded.getExpiresAt().toInstant(); + } catch (Exception e) { + return null; + } + } + +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/base/controller/BaseDataController.java b/code/link56/link56-backend/src/main/java/cn/link56/base/controller/BaseDataController.java new file mode 100644 index 00000000..d7923490 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/base/controller/BaseDataController.java @@ -0,0 +1,22 @@ +package cn.link56.base.controller; + +import cn.link56.base.model.vo.BaseDataVO; +import cn.link56.base.service.BaseDataService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/api/base") +public class BaseDataController { + @Resource + private BaseDataService baseDataService; + + + @GetMapping("/init") + public BaseDataVO initBaseData() { + return baseDataService.getBaseData(); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/base/mapper/IndustryMapper.java b/code/link56/link56-backend/src/main/java/cn/link56/base/mapper/IndustryMapper.java new file mode 100644 index 00000000..11cf0873 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/base/mapper/IndustryMapper.java @@ -0,0 +1,9 @@ +package cn.link56.base.mapper; + +import cn.link56.base.model.BIndustry; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface IndustryMapper extends BaseMapper { +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/base/model/BIndustry.java b/code/link56/link56-backend/src/main/java/cn/link56/base/model/BIndustry.java new file mode 100644 index 00000000..cdf47a09 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/base/model/BIndustry.java @@ -0,0 +1,27 @@ +package cn.link56.base.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName("b_industry") +@Accessors(chain = true) +public class BIndustry { + @TableId(value = "b_id") + private Long id; + @TableField("b_xh") + private Integer xh; + @TableField("b_name") + private String name; + @TableField("b_canuse") + private String canuse; + @TableField("b_bz") + private String bz; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/base/model/vo/BaseDataVO.java b/code/link56/link56-backend/src/main/java/cn/link56/base/model/vo/BaseDataVO.java new file mode 100644 index 00000000..9cb822cb --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/base/model/vo/BaseDataVO.java @@ -0,0 +1,34 @@ +package cn.link56.base.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BaseDataVO { + private List industries; + private List areas; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Industry { + private Long id; + private Integer xh; + private String name; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Area { + private String id; + private Integer xh; + private String name; + private String fatherId; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/base/service/BaseDataService.java b/code/link56/link56-backend/src/main/java/cn/link56/base/service/BaseDataService.java new file mode 100644 index 00000000..b88456aa --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/base/service/BaseDataService.java @@ -0,0 +1,38 @@ +package cn.link56.base.service; + +import cn.link56.base.mapper.IndustryMapper; +import cn.link56.base.model.BIndustry; +import cn.link56.base.model.vo.BaseDataVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class BaseDataService { + @Resource + private IndustryMapper industryMapper; + + public BaseDataVO getBaseData() { + BaseDataVO baseDataVO = new BaseDataVO(); + + // 查询可用行业 + List industries = industryMapper.selectList( + new LambdaQueryWrapper() + .eq(BIndustry::getCanuse, "1") + ).stream() + .map(industry -> new BaseDataVO.Industry( + industry.getId(), + industry.getXh(), + industry.getName() + )) + .collect(Collectors.toList()); + + + baseDataVO.setIndustries(industries); + + return baseDataVO; + } +} \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/Result.java b/code/link56/link56-backend/src/main/java/cn/link56/common/Result.java new file mode 100644 index 00000000..1f0b41d3 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/Result.java @@ -0,0 +1,51 @@ +package cn.link56.common; + +import cn.link56.common.constants.ResponseStatusConstants; +import lombok.Data; +import lombok.NonNull; + +@Data +public class Result { + private final Integer code; + private final String message; + private final T data; + + // 私有构造函数,强制通过静态工厂方法创建实例 + private Result(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + // 成功响应(无数据) + public static Result success() { + return new Result<>(ResponseStatusConstants.SUCCESS,"请求成功", null); + } + + public static Result success(String message) { + return new Result<>(ResponseStatusConstants.SUCCESS, message, null); + } + + // 成功响应(带数据) + public static Result success(@NonNull T data) { + return new Result<>(ResponseStatusConstants.SUCCESS,"请求成功", data); + } + + // 成功响应(带数据) + public static Result success(@NonNull T data,String message) { + return new Result<>(ResponseStatusConstants.SUCCESS, message, data); + } + + + public static Result error(Integer code,String message){ + return new Result<>(code, message, null); + } + + public static Result error(String message){ + return new Result<>(ResponseStatusConstants.ERROR, message, null); + } + + public static Result error(){ + return new Result<>(ResponseStatusConstants.ERROR, null,null); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/config/IdGeneratorInitializer.java b/code/link56/link56-backend/src/main/java/cn/link56/common/config/IdGeneratorInitializer.java new file mode 100644 index 00000000..d8625b89 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/config/IdGeneratorInitializer.java @@ -0,0 +1,19 @@ +package cn.link56.common.config; + +import cn.link56.common.utils.snowflake.contract.IdGeneratorOptions; +import cn.link56.common.utils.snowflake.idgen.IdGenerator; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class IdGeneratorInitializer { + @EventListener(ApplicationReadyEvent.class) + public void init() { + IdGeneratorOptions options = new IdGeneratorOptions(); + options.WorkerId = 1; + IdGenerator.setIdGenerator(options); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/config/MybatisPlusConfig.java b/code/link56/link56-backend/src/main/java/cn/link56/common/config/MybatisPlusConfig.java new file mode 100644 index 00000000..592c6969 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/config/MybatisPlusConfig.java @@ -0,0 +1,24 @@ +package cn.link56.common.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("cn.link56.**.mapper") +public class MybatisPlusConfig { + + /** + * 添加分页插件 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQL_SERVER2005)); // 如果配置多个插件, 切记分页最后添加 + // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType + return interceptor; + } +} \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/config/WebMvcConfig.java b/code/link56/link56-backend/src/main/java/cn/link56/common/config/WebMvcConfig.java new file mode 100644 index 00000000..f9224cca --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/config/WebMvcConfig.java @@ -0,0 +1,39 @@ +package cn.link56.common.config; + +import cn.link56.common.interceptor.AuthInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + private final AuthInterceptor authInterceptor; + + public WebMvcConfig(AuthInterceptor authInterceptor) { + this.authInterceptor = authInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/**") + .excludePathPatterns( + "/auth/**", + "/company/getCompanyPage", + "/company/getPublicDetail", + "/company/getRecommendList", + "/company/getRecommendRightList", + + "/forum/post/page", + "/forum/post/detail", + "/forum/post/getRecommendList", + "/forum/post/getHotList", + "/forum/post/comment/list", + + "/base/**", + + "/error/**", + "/favicon.ico" + ); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/constants/ResponseStatusConstants.java b/code/link56/link56-backend/src/main/java/cn/link56/common/constants/ResponseStatusConstants.java new file mode 100644 index 00000000..2ffee1d0 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/constants/ResponseStatusConstants.java @@ -0,0 +1,28 @@ +package cn.link56.common.constants; + +public class ResponseStatusConstants { + /** + * 操作成功 + */ + public static final Integer SUCCESS = 200; + + /** + * 系统内部错误 + */ + public static final Integer ERROR = 500; + + /** + * 缺少参数,参数格式有误,请求路径有误等 + */ + public static final Integer BAD_REQUEST = 400; + + /** + * 未找到资源,或者该资源已丢失 + */ + public static final Integer NOT_FOUND = 404; + + /** + * 登录失效或已过期 + */ + public static final Integer UNAUTHORIZED = 401; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/context/UserContext.java b/code/link56/link56-backend/src/main/java/cn/link56/common/context/UserContext.java new file mode 100644 index 00000000..3f6dfac2 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/context/UserContext.java @@ -0,0 +1,40 @@ +package cn.link56.common.context; + +/** + * 用户信息上下文(ThreadLocal) + * 独立,不依赖 JwtUtils 或拦截器 + */ +public class UserContext { + + private static final ThreadLocal USER_ID = new ThreadLocal<>(); + private static final ThreadLocal COMPANY_ID = new ThreadLocal<>(); + + public static void setUserId(Long userId) { + USER_ID.set(userId); + } + + public static Long getUserId() { + return USER_ID.get(); + } + + public static void removeUserId() { + USER_ID.remove(); + } + + public static void setCompanyId(Long companyId) { + COMPANY_ID.set(companyId); + } + + public static Long getCompanyId() { + return COMPANY_ID.get(); + } + + public static void removeCompanyId() { + COMPANY_ID.remove(); + } + + public static void clear() { + removeUserId(); + removeCompanyId(); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/exception/BusinessException.java b/code/link56/link56-backend/src/main/java/cn/link56/common/exception/BusinessException.java new file mode 100644 index 00000000..dd9ad87a --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/exception/BusinessException.java @@ -0,0 +1,7 @@ +package cn.link56.common.exception; + +public class BusinessException extends RuntimeException { + public BusinessException(String message) { + super(message); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/exception/GlobalExceptionHandler.java b/code/link56/link56-backend/src/main/java/cn/link56/common/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..965904b1 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,99 @@ +package cn.link56.common.exception; + +import cn.link56.common.Result; +import cn.link56.common.constants.ResponseStatusConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.ConstraintViolationException; +import java.util.Objects; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public Result handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + String message = "请求地址 '" + requestURI + "' 不支持 '" + e.getMethod() + " ' 请求"; + return Result.error(ResponseStatusConstants.BAD_REQUEST,message); + } + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public Result handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + return Result.error(ResponseStatusConstants.BAD_REQUEST,String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public Result handleBindException(BindException e) { + log.error(e.getMessage(), e); + return Result.error(ResponseStatusConstants.BAD_REQUEST,"自定义校验异常"); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage(); + return Result.error(ResponseStatusConstants.BAD_REQUEST,message); + } + + /** + * 缺少参数异常 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + return Result.error(ResponseStatusConstants.BAD_REQUEST,"缺少请求体:" + e.getMessage()); + } + + /** + * 缺少参数异常 + */ + @ExceptionHandler(MissingServletRequestParameterException.class) + public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { + return Result.error(ResponseStatusConstants.BAD_REQUEST,"缺少参数:" + e.getMessage()); + } + + /** + * 缺少参数异常 + */ + @ExceptionHandler(ConstraintViolationException.class) + public Result handleConstraintViolationException(ConstraintViolationException e) { + return Result.error(ResponseStatusConstants.BAD_REQUEST,e.getMessage()); + } + + // 自定义异常 + @ExceptionHandler(OperationTooFrequentException.class) + public Result handleOperationTooFrequentException(OperationTooFrequentException e) { + return Result.error(e.getMessage()); + } + + /** + * 处理系统异常 + */ + @ExceptionHandler(value = Exception.class) + public Result handleSystemException(Exception e) { + e.printStackTrace(); + log.error("捕获到全局异常:{}",e.getMessage()); + return Result.error(e.getMessage()); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/exception/OperationTooFrequentException.java b/code/link56/link56-backend/src/main/java/cn/link56/common/exception/OperationTooFrequentException.java new file mode 100644 index 00000000..bb24d3a3 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/exception/OperationTooFrequentException.java @@ -0,0 +1,11 @@ +package cn.link56.common.exception; + +public class OperationTooFrequentException extends BusinessException { + public OperationTooFrequentException(String message) { + super(message); + } + + public OperationTooFrequentException() { + super("操作过于频繁,请稍后再试"); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/interceptor/AuthInterceptor.java b/code/link56/link56-backend/src/main/java/cn/link56/common/interceptor/AuthInterceptor.java new file mode 100644 index 00000000..35677986 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/interceptor/AuthInterceptor.java @@ -0,0 +1,89 @@ +package cn.link56.common.interceptor; + +import cn.link56.auth.token.model.TokenPayload; +import cn.link56.auth.token.service.AuthTokenService; +import cn.link56.common.Result; +import cn.link56.common.constants.ResponseStatusConstants; +import cn.link56.common.context.UserContext; +import cn.link56.common.utils.JsonUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; + +@Component +public class AuthInterceptor implements HandlerInterceptor { + + private final AuthTokenService authTokenService; + + public AuthInterceptor(AuthTokenService authTokenService) { + this.authTokenService = authTokenService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + // 预检请求放行 + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } + System.out.println(request.getRequestURI()); + System.out.println(request.getRequestURL()); + + try { + String token = request.getHeader("Authorization"); + if (StringUtils.isBlank(token)) { + return writeUnauthorized(response); + } + + boolean flag = authTokenService.validateToken(token); + + if (!flag){ + return writeUnauthorized(response); + } + + // 解析 token + TokenPayload tokenPayload = authTokenService.parsePayload(token); + + // 对比信息 + String companyIdStr = request.getHeader("X-Company-Id"); + String userIdStr = request.getHeader("X-User-Id"); + + if ((StringUtils.isNotBlank(companyIdStr) && !companyIdStr.equals(tokenPayload.getCompanyId().toString())) + || (StringUtils.isNotBlank(userIdStr) && !userIdStr.equals(tokenPayload.getUserId().toString()))) { + return writeUnauthorized(response); + } + + // 注入到 UserContext + UserContext.setUserId(tokenPayload.getUserId()); + UserContext.setCompanyId(tokenPayload.getCompanyId()); + + return true; + + } catch (Exception e) { + return writeUnauthorized(response); + } + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + UserContext.clear(); // 清理 ThreadLocal + } + + private boolean writeUnauthorized(HttpServletResponse response) throws Exception { + Result result = Result.error(ResponseStatusConstants.UNAUTHORIZED, "登录过期或未授权"); + String jsonString = JsonUtils.toJson(result); + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=utf-8"); + + try (PrintWriter out = response.getWriter()) { + out.println(jsonString); + out.flush(); + } + return false; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/EmailUtils.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/EmailUtils.java new file mode 100644 index 00000000..81395d1a --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/EmailUtils.java @@ -0,0 +1,78 @@ +package cn.link56.common.utils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import javax.annotation.Resource; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +@Component +public class EmailUtils { + + @Value("${spring.mail.username}") + private String fromEmail; + + @Resource + private JavaMailSender mailSender; + + @Resource + private TemplateEngine templateEngine; + + /** + * 异步发送邮件 + */ + @Async + public CompletableFuture sendAsync(String templateHtml, String subject, Map mapValue, String toEmail) { + try { + MimeMessage message = createMimeMessage(templateHtml, subject, mapValue, toEmail); + mailSender.send(message); + return CompletableFuture.completedFuture(true); + } catch (Exception e) { + System.err.println("Error sending async email: " + e.getMessage()); + return CompletableFuture.completedFuture(false); + } + } + + /** + * 同步发送邮件 + */ + public boolean sendSync(String templateHtml, String subject, Map mapValue, String toEmail) { + try { + MimeMessage message = createMimeMessage(templateHtml, subject, mapValue, toEmail); + mailSender.send(message); + return true; + } catch (Exception e) { + System.err.println("Error sending sync email: " + e.getMessage()); + return false; + } + } + + /** + * 构建 MimeMessage + */ + private MimeMessage createMimeMessage(String templateHtml, String subject, Map mapValue, String toEmail) throws MessagingException { + Context context = new Context(); + if (mapValue != null) { + mapValue.forEach(context::setVariable); + } + + String emailContent = templateEngine.process(templateHtml, context); + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + + helper.setFrom(fromEmail); + helper.setTo(toEmail); + helper.setSubject(subject); + helper.setText(emailContent, true); + + return message; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/JsonUtils.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/JsonUtils.java new file mode 100644 index 00000000..a7b17423 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/JsonUtils.java @@ -0,0 +1,40 @@ +package cn.link56.common.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JsonUtils { + private static final ObjectMapper objectMapper = new ObjectMapper(); + // 判断字符串是否为有效的 JSON 格式 + public static boolean isTypeJSON(String msgText) { + try { + // 尝试将字符串解析为 JsonNode,如果能解析,说明是有效的 JSON + JsonNode node = objectMapper.readTree(msgText); + return node != null; + } catch (Exception e) { + return false; // 如果抛出异常,说明不是有效的 JSON + } + } + + // 将 JSON 字符串转换为指定类型的 Java 对象 + public static T toBean(String json, Class clazz) { + try { + // 使用 ObjectMapper 将 JSON 字符串转换为指定类型的对象 + return objectMapper.readValue(json, clazz); + } catch (JsonProcessingException e) { + e.printStackTrace(); // 打印异常信息 + return null; // 如果转换失败,返回 null + } + } + + // 将对象转换为 JSON 字符串 + public static String toJson(Object obj) { + try { + return objectMapper.writeValueAsString(obj); // 将对象转换为 JSON 字符串 + } catch (JsonProcessingException e) { + System.out.println(e.getMessage()); + return null; // 如果出现异常,返回 null + } + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IIdGenerator.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IIdGenerator.java new file mode 100644 index 00000000..9d743e5e --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IIdGenerator.java @@ -0,0 +1,5 @@ +package cn.link56.common.utils.snowflake.contract; + +public interface IIdGenerator { + long newLong() throws IdGeneratorException; +} \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/ISnowWorker.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/ISnowWorker.java new file mode 100644 index 00000000..fd2c43d4 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/ISnowWorker.java @@ -0,0 +1,5 @@ +package cn.link56.common.utils.snowflake.contract; + +public interface ISnowWorker { + long nextId() throws IdGeneratorException; +} \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IdGeneratorException.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IdGeneratorException.java new file mode 100644 index 00000000..26aa1417 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IdGeneratorException.java @@ -0,0 +1,25 @@ +package cn.link56.common.utils.snowflake.contract; + +public class IdGeneratorException extends RuntimeException { + + public IdGeneratorException() { + super(); + } + + public IdGeneratorException(String message) { + super(message); + } + + public IdGeneratorException(Throwable cause) { + super(cause); + } + + public IdGeneratorException(String message, Throwable cause) { + super(message, cause); + } + + public IdGeneratorException(String msgFormat, Object... args) { + super(String.format(msgFormat, args)); + } + +} \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IdGeneratorOptions.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IdGeneratorOptions.java new file mode 100644 index 00000000..71227908 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/IdGeneratorOptions.java @@ -0,0 +1,59 @@ +package cn.link56.common.utils.snowflake.contract; + +public class IdGeneratorOptions { + /** + * 雪花计算方法 + * (1-漂移算法|2-传统算法),默认1 + */ + public short Method = 1; + + /** + * 基础时间(ms单位) + * 不能超过当前系统时间 + */ + public long BaseTime = 1582136402000L; + + /** + * 机器码 + * 必须由外部设定,最大值 2^WorkerIdBitLength-1 + */ + public short WorkerId = 0; + + /** + * 机器码位长 + * 默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22) + */ + public byte WorkerIdBitLength = 6; + + /** + * 序列数位长 + * 默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22) + */ + public byte SeqBitLength = 6; + + /** + * 最大序列数(含) + * 设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1]) + */ + public short MaxSeqNumber = 0; + + /** + * 最小序列数(含) + * 默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号是0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位 + */ + public short MinSeqNumber = 5; + + /** + * 最大漂移次数(含) + * 默认2000,推荐范围500-10000(与计算能力有关) + */ + public short TopOverCostCount = 2000; + + public IdGeneratorOptions() { + + } + + public IdGeneratorOptions(short workerId) { + WorkerId = workerId; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/OverCostActionArg.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/OverCostActionArg.java new file mode 100644 index 00000000..c0301618 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/contract/OverCostActionArg.java @@ -0,0 +1,45 @@ +package cn.link56.common.utils.snowflake.contract; +/** + * Id生成时回调参数 + */ +public class OverCostActionArg { + /** + * 事件类型 + * 1-开始,2-结束,8-漂移 + */ + public int ActionType = 0; + + /** + * 时间戳 + */ + public long TimeTick = 0; + + /** + * 机器码 + */ + public short WorkerId = 0; + + /** + * + */ + public int OverCostCountInOneTerm = 0; + + /** + * 漂移期间生产ID个数 + */ + public int GenCountInOneTerm = 0; + + /** + * 漂移周期 + */ + public int TermIndex = 0; + + public OverCostActionArg(short workerId, long timeTick, int actionType, int overCostCountInOneTerm, int genCountWhenOverCost, int index) { + ActionType = actionType; + TimeTick = timeTick; + WorkerId = workerId; + OverCostCountInOneTerm = overCostCountInOneTerm; + GenCountInOneTerm = genCountWhenOverCost; + TermIndex = index; + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/core/SnowWorkerM1.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/core/SnowWorkerM1.java new file mode 100644 index 00000000..fede1a9f --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/core/SnowWorkerM1.java @@ -0,0 +1,229 @@ +package cn.link56.common.utils.snowflake.core; + +import cn.link56.common.utils.snowflake.contract.ISnowWorker; +import cn.link56.common.utils.snowflake.contract.IdGeneratorException; +import cn.link56.common.utils.snowflake.contract.IdGeneratorOptions; +import cn.link56.common.utils.snowflake.contract.OverCostActionArg; + +public class SnowWorkerM1 implements ISnowWorker { + /** + * 基础时间 + */ + protected final long BaseTime; + + /** + * 机器码 + */ + protected final short WorkerId; + + /** + * 机器码位长 + */ + protected final byte WorkerIdBitLength; + + /** + * 自增序列数位长 + */ + protected final byte SeqBitLength; + + /** + * 最大序列数(含) + */ + protected final int MaxSeqNumber; + + /** + * 最小序列数(含) + */ + protected final short MinSeqNumber; + + /** + * 最大漂移次数(含) + */ + protected final int TopOverCostCount; + + protected final byte _TimestampShift; + protected final static byte[] _SyncLock = new byte[0]; + + protected short _CurrentSeqNumber; + protected long _LastTimeTick = 0; + protected long _TurnBackTimeTick = 0; + protected byte _TurnBackIndex = 0; + + protected boolean _IsOverCost = false; + protected int _OverCostCountInOneTerm = 0; + protected int _GenCountInOneTerm = 0; + protected int _TermIndex = 0; + + public SnowWorkerM1(IdGeneratorOptions options) { + BaseTime = options.BaseTime != 0 ? options.BaseTime : 1582136402000L; + WorkerIdBitLength = options.WorkerIdBitLength == 0 ? 6 : options.WorkerIdBitLength; + WorkerId = options.WorkerId; + SeqBitLength = options.SeqBitLength == 0 ? 6 : options.SeqBitLength; + MaxSeqNumber = options.MaxSeqNumber <= 0 ? (1 << SeqBitLength) - 1 : options.MaxSeqNumber; + MinSeqNumber = options.MinSeqNumber; + // TopOverCostCount = options.TopOverCostCount == 0 ? 2000 : options.TopOverCostCount; + TopOverCostCount = options.TopOverCostCount; + _TimestampShift = (byte) (WorkerIdBitLength + SeqBitLength); + _CurrentSeqNumber = MinSeqNumber; + } + + private void DoGenIdAction(OverCostActionArg arg) { + + } + + private void BeginOverCostAction(long useTimeTick) { + + } + + private void EndOverCostAction(long useTimeTick) { + // if (_TermIndex > 10000) { + // _TermIndex = 0; + // } + } + + private void BeginTurnBackAction(long useTimeTick) { + + } + + private void EndTurnBackAction(long useTimeTick) { + + } + + private long NextOverCostId() { + long currentTimeTick = GetCurrentTimeTick(); + + if (currentTimeTick > _LastTimeTick) { + EndOverCostAction(currentTimeTick); + + _LastTimeTick = currentTimeTick; + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = false; + _OverCostCountInOneTerm = 0; + _GenCountInOneTerm = 0; + + return CalcId(_LastTimeTick); + } + + if (_OverCostCountInOneTerm >= TopOverCostCount) { + EndOverCostAction(currentTimeTick); + + _LastTimeTick = GetNextTimeTick(); + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = false; + _OverCostCountInOneTerm = 0; + _GenCountInOneTerm = 0; + + return CalcId(_LastTimeTick); + } + + if (_CurrentSeqNumber > MaxSeqNumber) { + _LastTimeTick++; + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = true; + _OverCostCountInOneTerm++; + _GenCountInOneTerm++; + + return CalcId(_LastTimeTick); + } + + _GenCountInOneTerm++; + return CalcId(_LastTimeTick); + } + + private long NextNormalId() throws IdGeneratorException { + long currentTimeTick = GetCurrentTimeTick(); + + if (currentTimeTick < _LastTimeTick) { + if (_TurnBackTimeTick < 1) { + _TurnBackTimeTick = _LastTimeTick - 1; + _TurnBackIndex++; + // 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序 + // 支持4次回拨次序(避免回拨重叠导致ID重复),可无限次回拨(次序循环使用)。 + if (_TurnBackIndex > 4) { + _TurnBackIndex = 1; + } + //BeginTurnBackAction(_TurnBackTimeTick); + } + + // try { + // Thread.sleep(1); + // } catch (InterruptedException e) { + // e.printStackTrace(); + // } + + return CalcTurnBackId(_TurnBackTimeTick); + } + + // 时间追平时,_TurnBackTimeTick清零 + if (_TurnBackTimeTick > 0) { + EndTurnBackAction(_TurnBackTimeTick); + _TurnBackTimeTick = 0; + } + + if (currentTimeTick > _LastTimeTick) { + _LastTimeTick = currentTimeTick; + _CurrentSeqNumber = MinSeqNumber; + + return CalcId(_LastTimeTick); + } + + if (_CurrentSeqNumber > MaxSeqNumber) { + BeginOverCostAction(currentTimeTick); + + _TermIndex++; + _LastTimeTick++; + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = true; + _OverCostCountInOneTerm = 1; + _GenCountInOneTerm = 1; + + return CalcId(_LastTimeTick); + } + + return CalcId(_LastTimeTick); + } + + private long CalcId(long useTimeTick) { + long result = ((useTimeTick << _TimestampShift) + + ((long) WorkerId << SeqBitLength) + + (int) _CurrentSeqNumber); + + _CurrentSeqNumber++; + return result; + } + + private long CalcTurnBackId(long useTimeTick) { + long result = ((useTimeTick << _TimestampShift) + + ((long) WorkerId << SeqBitLength) + _TurnBackIndex); + + _TurnBackTimeTick--; + return result; + } + + protected long GetCurrentTimeTick() { + long millis = System.currentTimeMillis(); + return millis - BaseTime; + } + + protected long GetNextTimeTick() { + long tempTimeTicker = GetCurrentTimeTick(); + + while (tempTimeTicker <= _LastTimeTick) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + tempTimeTicker = GetCurrentTimeTick(); + } + + return tempTimeTicker; + } + + @Override + public long nextId() { + synchronized (_SyncLock) { + return _IsOverCost ? NextOverCostId() : NextNormalId(); + } + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/core/SnowWorkerM2.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/core/SnowWorkerM2.java new file mode 100644 index 00000000..4f74ee1c --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/core/SnowWorkerM2.java @@ -0,0 +1,36 @@ +package cn.link56.common.utils.snowflake.core; + +import cn.link56.common.utils.snowflake.contract.IdGeneratorException; +import cn.link56.common.utils.snowflake.contract.IdGeneratorOptions; + +public class SnowWorkerM2 extends SnowWorkerM1 { + public SnowWorkerM2(IdGeneratorOptions options) { + super(options); + } + + @Override + public long nextId() { + synchronized (_SyncLock) { + long currentTimeTick = GetCurrentTimeTick(); + + if (_LastTimeTick == currentTimeTick) { + if (_CurrentSeqNumber++ > MaxSeqNumber) { + _CurrentSeqNumber = MinSeqNumber; + currentTimeTick = GetNextTimeTick(); + } + } else { + _CurrentSeqNumber = MinSeqNumber; + } + + if (currentTimeTick < _LastTimeTick) { + throw new IdGeneratorException("Time error for {0} milliseconds", _LastTimeTick - currentTimeTick); + } + + _LastTimeTick = currentTimeTick; + long result = ((currentTimeTick << _TimestampShift) + ((long) WorkerId << SeqBitLength) + (int) _CurrentSeqNumber); + + return result; + } + + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/idgen/DefaultIdGenerator.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/idgen/DefaultIdGenerator.java new file mode 100644 index 00000000..ffc8ca0a --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/idgen/DefaultIdGenerator.java @@ -0,0 +1,88 @@ +package cn.link56.common.utils.snowflake.idgen; + +import cn.link56.common.utils.snowflake.contract.IIdGenerator; +import cn.link56.common.utils.snowflake.contract.ISnowWorker; +import cn.link56.common.utils.snowflake.contract.IdGeneratorException; +import cn.link56.common.utils.snowflake.contract.IdGeneratorOptions; +import cn.link56.common.utils.snowflake.core.SnowWorkerM1; +import cn.link56.common.utils.snowflake.core.SnowWorkerM2; + +public class DefaultIdGenerator implements IIdGenerator { + private static ISnowWorker _SnowWorker = null; + + public DefaultIdGenerator(IdGeneratorOptions options) throws IdGeneratorException { + if (options == null) { + throw new IdGeneratorException("options error."); + } + + // 1.BaseTime + if (options.BaseTime < 315504000000L || options.BaseTime > System.currentTimeMillis()) { + throw new IdGeneratorException("BaseTime error."); + } + + // 2.WorkerIdBitLength + if (options.WorkerIdBitLength <= 0) { + throw new IdGeneratorException("WorkerIdBitLength error.(range:[1, 21])"); + } + if (options.WorkerIdBitLength + options.SeqBitLength > 22) { + throw new IdGeneratorException("error:WorkerIdBitLength + SeqBitLength <= 22"); + } + + // 3.WorkerId + int maxWorkerIdNumber = (1 << options.WorkerIdBitLength) - 1; + if (maxWorkerIdNumber == 0) { + maxWorkerIdNumber = 63; + } + if (options.WorkerId < 0 || options.WorkerId > maxWorkerIdNumber) { + throw new IdGeneratorException( + "WorkerId error. (range:[0, " + (maxWorkerIdNumber > 0 ? maxWorkerIdNumber : 63) + "]"); + } + + // 4.SeqBitLength + if (options.SeqBitLength < 2 || options.SeqBitLength > 21) { + throw new IdGeneratorException("SeqBitLength error. (range:[2, 21])"); + } + + // 5.MaxSeqNumber + int maxSeqNumber = (1 << options.SeqBitLength) - 1; + if (maxSeqNumber == 0) { + maxSeqNumber = 63; + } + if (options.MaxSeqNumber < 0 || options.MaxSeqNumber > maxSeqNumber) { + throw new IdGeneratorException("MaxSeqNumber error. (range:[1, " + maxSeqNumber + "]"); + } + + // 6.MinSeqNumber + if (options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber) { + throw new IdGeneratorException("MinSeqNumber error. (range:[5, " + maxSeqNumber + "]"); + } + + // 7.TopOverCostCount + if (options.TopOverCostCount < 0 || options.TopOverCostCount > 10000) { + throw new IdGeneratorException("TopOverCostCount error. (range:[0, 10000]"); + } + + switch (options.Method) { + case 2: + _SnowWorker = new SnowWorkerM2(options); + break; + case 1: + default: + _SnowWorker = new SnowWorkerM1(options); + break; + } + + if (options.Method == 1) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + @Override + public long newLong() { + return _SnowWorker.nextId(); + } +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/idgen/IdGenerator.java b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/idgen/IdGenerator.java new file mode 100644 index 00000000..693b51a2 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/common/utils/snowflake/idgen/IdGenerator.java @@ -0,0 +1,34 @@ +package cn.link56.common.utils.snowflake.idgen; + +import cn.link56.common.utils.snowflake.contract.IIdGenerator; +import cn.link56.common.utils.snowflake.contract.IdGeneratorException; +import cn.link56.common.utils.snowflake.contract.IdGeneratorOptions; +import lombok.Getter; + +public class IdGenerator { + @Getter + private static IIdGenerator idGenInstance = null; + + /** + * 设置参数,建议程序初始化时执行一次 + */ + public static void setIdGenerator(IdGeneratorOptions options) throws IdGeneratorException { + idGenInstance = new DefaultIdGenerator(options); + } + + /** + * 生成新的Id + * 调用本方法前,请确保调用了 SetIdGenerator 方法做初始化。 + * + */ + public static long nextId() throws IdGeneratorException { + // if (idGenInstance == null) { + // idGenInstance = new DefaultIdGenerator(new IdGeneratorOptions((short) 1)); + // } + + if (idGenInstance == null) + throw new IdGeneratorException("Please initialize Yitter.IdGeneratorOptions first."); + + return idGenInstance.newLong(); + } +} diff --git a/code/link56/link56-backend/src/main/resources/application-dev.yml b/code/link56/link56-backend/src/main/resources/application-dev.yml new file mode 100644 index 00000000..69a48fc9 --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/application-dev.yml @@ -0,0 +1,55 @@ +server: + port: 8084 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: net.sourceforge.jtds.jdbc.Driver + url: jdbc:jtds:sqlserver://192.168.1.189:1433;databaseName=G3-Link + username: sa + password: "g3soft" + # druid 连接池管理 + druid: + # 初始化时建立物理连接的个数 + initial-size: 5 + # 连接池的最小空闲数量 + min-idle: 5 + # 连接池最大连接数量 + max-active: 20 + # 获取连接时最大等待时间,单位毫秒 + max-wait: 60000 + # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 + test-while-idle: true + # 既作为检测的间隔时间又作为testWhileIdel执行的依据 + time-between-eviction-runs-millis: 60000 + # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间) + min-evictable-idle-time-millis: 30000 + # 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual) validation-query: select 'x' + # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true + test-on-borrow: false + # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true + test-on-return: false + # 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。 + pool-prepared-statements: false + # 置监控统计拦截的filters,去掉后监控界面sql无法统计,stat: 监控统计、Slf4j:日志记录、waLL: 防御sqL注入 + filters: stat,wall,slf4j + # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 + max-pool-prepared-statement-per-connection-size: -1 + # 合并多个DruidDataSource的监控数据 + use-global-data-source-stat: true + # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 + connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 + + stat-view-servlet: + # 是否启用StatViewServlet默认值true + enabled: true + # 访问路径为/druid时,跳转到StatViewServlet + url-pattern: /druid/* + # 是否能够重置数据 + reset-enable: false + # 需要账号密码才能访问控制台,默认为root + login-username: root + login-password: root + # IP白名单 + allow: 127.0.0.1 + # IP黑名单(共同存在时,deny优先于allow) + deny: \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/resources/application.yml b/code/link56/link56-backend/src/main/resources/application.yml new file mode 100644 index 00000000..9468e50c --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/application.yml @@ -0,0 +1,36 @@ +server: + servlet: + context-path: / # 应用的访问路径 + tomcat: + uri-encoding: UTF-8 # URI编码 + accept-count: 1000 # 连接数满后的排队数 + threads: + max: 800 # 最大线程数 + min-spare: 100 # 初始化的线程数 + max-http-header-size: 64KB +logging: + level: + com.client: debug # 调试级别的日志 + org.springframework: warn # 仅显示警告级别及以上的Spring日志 + +spring: + profiles: + active: dev + mail: + host: smtp.163.com #邮箱服务器地址 + username: hi_itao@163.com #邮箱账号 + password: GVdEzKAee9MByXdy #邮箱授权码 + default-encoding: utf-8 #默认编码 + +mybatis-plus: + # 对应的 XML 文件位置 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 实体扫描,多个package用逗号或者分号分隔 + typeAliasesPackage: cn.link56.domain + # configLocation: classpath:mybatis/mybatis-config.xml + configuration: + cache-enabled: true + use-generated-keys: true + default-executor-type: simple + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/resources/logback.xml b/code/link56/link56-backend/src/main/resources/logback.xml new file mode 100644 index 00000000..f93cf29e --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/logback.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/resources/templates/ForgetPassword.html b/code/link56/link56-backend/src/main/resources/templates/ForgetPassword.html new file mode 100644 index 00000000..d93928cf --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/templates/ForgetPassword.html @@ -0,0 +1,536 @@ + + + + + + + + + + + + + + + +Use this link to reset your password. The link is only valid for 24 hours. + + + + + + + + \ No newline at end of file diff --git a/code/link56/link56-backend/src/main/resources/templates/Register.html b/code/link56/link56-backend/src/main/resources/templates/Register.html new file mode 100644 index 00000000..32c0dd6c --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/templates/Register.html @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + +Use this link to reset your password. The link is only valid for 24 hours. + + + + + + + + \ No newline at end of file diff --git a/code/link56/link56-frontend-new/.env.test b/code/link56/link56-frontend-new/.env.test index 087626b9..88c5adfe 100644 --- a/code/link56/link56-frontend-new/.env.test +++ b/code/link56/link56-frontend-new/.env.test @@ -1,2 +1,2 @@ -VITE_SERVER_BASEURL = 'http://127.0.0.1:8083' +VITE_SERVER_BASEURL = 'http://127.0.0.1:8084' VITE_WEBSOCKET_BASEURL = 'ws://127.0.0.1:8083/websocket' \ No newline at end of file diff --git a/code/link56/link56-frontend-new/src/request/api/auth.ts b/code/link56/link56-frontend-new/src/request/api/auth.ts index 5cc421f6..caff6935 100644 --- a/code/link56/link56-frontend-new/src/request/api/auth.ts +++ b/code/link56/link56-frontend-new/src/request/api/auth.ts @@ -10,3 +10,4 @@ export function registerApi(params: registerApiParams) { export function forgetApi(params: forgetApiParams) { return httpPostJson(`/auth/forget`, params) } + diff --git a/code/link56/link56-frontend-new/src/request/api/auth/code.ts b/code/link56/link56-frontend-new/src/request/api/auth/code.ts new file mode 100644 index 00000000..b61c0c7e --- /dev/null +++ b/code/link56/link56-frontend-new/src/request/api/auth/code.ts @@ -0,0 +1,10 @@ +import type { AuthCodeSendParams, AuthCodeSendResult, AuthCodeVerifyParams, AuthCodeVerifyResult } from '@/types/auth/code' +import { httpPostJson } from '@/request/utils' + +export function authCodeSendApi(params: AuthCodeSendParams) { + return httpPostJson(`/auth/code/send`, params) +} + +export function authCodeVerifyApi(params: AuthCodeVerifyParams) { + return httpPostJson(`/auth/code/verify`, params) +} diff --git a/code/link56/link56-frontend-new/src/request/api/industry.ts b/code/link56/link56-frontend-new/src/request/api/industry.ts index 1f2fede7..4866f961 100644 --- a/code/link56/link56-frontend-new/src/request/api/industry.ts +++ b/code/link56/link56-frontend-new/src/request/api/industry.ts @@ -2,5 +2,5 @@ import type { IndustryResult } from '@/types/company/index' import { httpGet } from '../utils' export function getPropertyListApi() { - return httpGet(`/webProperty/list`) + return httpGet(`/base/industry/list`) } diff --git a/code/link56/link56-frontend-new/src/request/index.ts b/code/link56/link56-frontend-new/src/request/index.ts index cb355226..b70ea6d5 100644 --- a/code/link56/link56-frontend-new/src/request/index.ts +++ b/code/link56/link56-frontend-new/src/request/index.ts @@ -124,13 +124,4 @@ function isDownloadResponse(headers: any): boolean { return /application\/(?:octet-stream|force-download|pdf|vnd.ms-excel|msexcel|OCTET-STREAM)/.test(contentType || '') } -function parseJsonOrReturnString(input: any) { - try { - return JSON.parse(input) - } - catch { - return input - } -} - export default axiosInstance diff --git a/code/link56/link56-frontend-new/src/types/auth/code.ts b/code/link56/link56-frontend-new/src/types/auth/code.ts new file mode 100644 index 00000000..b0e3c709 --- /dev/null +++ b/code/link56/link56-frontend-new/src/types/auth/code.ts @@ -0,0 +1,31 @@ +export enum AuthCodeChannel { + EMAIL = 'EMAIL', + SMS = 'SMS', +} + +export enum AuthCodePurpose { + REGISTER = 'REGISTER', + RESET_PASSWORD = 'RESET_PASSWORD', + LOGIN = 'LOGIN', +} + +export interface AuthCodeSendParams { + purpose: AuthCodePurpose + channel: AuthCodeChannel + target: string +} + +export interface AuthCodeSendResult { + +} + +export interface AuthCodeVerifyParams { + purpose: AuthCodePurpose + channel: AuthCodeChannel + target: string + code: string +} + +export interface AuthCodeVerifyResult { + +} diff --git a/code/link56/link56-frontend-new/src/types/company/index.ts b/code/link56/link56-frontend-new/src/types/company/index.ts index 8f93ad87..84f4e0f7 100644 --- a/code/link56/link56-frontend-new/src/types/company/index.ts +++ b/code/link56/link56-frontend-new/src/types/company/index.ts @@ -10,6 +10,7 @@ export interface Industry { label: string value: string } + export type IndustryResult = Industry[] export interface CompanyQuery { diff --git a/code/link56/link56-frontend-new/src/views/auth/register.vue b/code/link56/link56-frontend-new/src/views/auth/register.vue deleted file mode 100644 index c4e26779..00000000 --- a/code/link56/link56-frontend-new/src/views/auth/register.vue +++ /dev/null @@ -1,331 +0,0 @@ - - - - - diff --git a/code/link56/link56-frontend-new/src/views/auth/register/components/AdminForm.vue b/code/link56/link56-frontend-new/src/views/auth/register/components/AdminForm.vue index 8a9f8d26..151cc268 100644 --- a/code/link56/link56-frontend-new/src/views/auth/register/components/AdminForm.vue +++ b/code/link56/link56-frontend-new/src/views/auth/register/components/AdminForm.vue @@ -1,13 +1,18 @@ - - - -