feat: add hera backend
This commit is contained in:
1 parent
5b906bf8f3
commit
4222b7124c
34 files changed
+2023
-83
No files matched your search
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class HeraApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HeraApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import com.hongzhen.hera.auth.AuthDtos.BindPhoneRequest;
|
||||
import com.hongzhen.hera.auth.AuthDtos.LoginResponse;
|
||||
import com.hongzhen.hera.auth.AuthDtos.SendSmsRequest;
|
||||
import com.hongzhen.hera.auth.AuthDtos.WxLoginRequest;
|
||||
import com.hongzhen.hera.auth.AuthDtos.WxLoginResponse;
|
||||
import com.hongzhen.hera.common.ApiResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
private final SmsService smsService;
|
||||
|
||||
public AuthController(AuthService authService, SmsService smsService) {
|
||||
this.authService = authService;
|
||||
this.smsService = smsService;
|
||||
}
|
||||
|
||||
@PostMapping("/wx-login")
|
||||
public ApiResponse<WxLoginResponse> wxLogin(@Valid @RequestBody WxLoginRequest request) {
|
||||
return ApiResponse.ok(authService.wxLogin(request.getCode()));
|
||||
}
|
||||
|
||||
@PostMapping("/sms")
|
||||
public ApiResponse<Map<String, String>> sendSms(@Valid @RequestBody SendSmsRequest request) {
|
||||
String code = smsService.send(request.getPhone());
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
data.put("mockCode", code);
|
||||
return ApiResponse.ok(data);
|
||||
}
|
||||
|
||||
@PostMapping("/bind-phone")
|
||||
public ApiResponse<LoginResponse> bindPhone(@Valid @RequestBody BindPhoneRequest request) {
|
||||
return ApiResponse.ok(authService.bindPhone(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import com.hongzhen.hera.user.ProfileDtos.ProfileResponse;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
public class AuthDtos {
|
||||
|
||||
public static class WxLoginRequest {
|
||||
@NotBlank
|
||||
private String code;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WxLoginResponse {
|
||||
private final String sessionKey;
|
||||
private final boolean phoneBound;
|
||||
|
||||
public WxLoginResponse(String sessionKey, boolean phoneBound) {
|
||||
this.sessionKey = sessionKey;
|
||||
this.phoneBound = phoneBound;
|
||||
}
|
||||
|
||||
public String getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public boolean isPhoneBound() {
|
||||
return phoneBound;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SendSmsRequest {
|
||||
@Pattern(regexp = "^1\\d{10}$")
|
||||
private String phone;
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BindPhoneRequest {
|
||||
@NotBlank
|
||||
private String sessionKey;
|
||||
@Pattern(regexp = "^1\\d{10}$")
|
||||
private String phone;
|
||||
@NotBlank
|
||||
private String captcha;
|
||||
|
||||
public String getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public String getCaptcha() {
|
||||
return captcha;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginResponse {
|
||||
private final String token;
|
||||
private final ProfileResponse user;
|
||||
|
||||
public LoginResponse(String token, ProfileResponse user) {
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public ProfileResponse getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import com.hongzhen.hera.auth.AuthDtos.BindPhoneRequest;
|
||||
import com.hongzhen.hera.auth.AuthDtos.LoginResponse;
|
||||
import com.hongzhen.hera.auth.AuthDtos.WxLoginResponse;
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import com.hongzhen.hera.security.JwtService;
|
||||
import com.hongzhen.hera.user.User;
|
||||
import com.hongzhen.hera.user.UserRepository;
|
||||
import com.hongzhen.hera.user.UserService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
private final WechatClient wechatClient;
|
||||
private final LoginSessionRepository loginSessionRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final SmsService smsService;
|
||||
private final JwtService jwtService;
|
||||
private final UserService userService;
|
||||
|
||||
public AuthService(
|
||||
WechatClient wechatClient,
|
||||
LoginSessionRepository loginSessionRepository,
|
||||
UserRepository userRepository,
|
||||
SmsService smsService,
|
||||
JwtService jwtService,
|
||||
UserService userService
|
||||
) {
|
||||
this.wechatClient = wechatClient;
|
||||
this.loginSessionRepository = loginSessionRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.smsService = smsService;
|
||||
this.jwtService = jwtService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WxLoginResponse wxLogin(String code) {
|
||||
WxSession wxSession = wechatClient.code2Session(code);
|
||||
String sessionKey = UUID.randomUUID().toString();
|
||||
LoginSession loginSession = new LoginSession();
|
||||
loginSession.setSessionKey(sessionKey);
|
||||
loginSession.setOpenId(wxSession.getOpenId());
|
||||
loginSession.setUnionId(wxSession.getUnionId());
|
||||
loginSession.setExpiresAt(LocalDateTime.now().plusMinutes(15));
|
||||
loginSessionRepository.save(loginSession);
|
||||
boolean phoneBound = userRepository.findByOpenId(wxSession.getOpenId()).map(user -> user.getPhone() != null).orElse(false);
|
||||
return new WxLoginResponse(sessionKey, phoneBound);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LoginResponse bindPhone(BindPhoneRequest request) {
|
||||
smsService.verify(request.getPhone(), request.getCaptcha());
|
||||
LoginSession session = loginSessionRepository.findBySessionKey(request.getSessionKey())
|
||||
.orElseThrow(() -> new BusinessException("登录会话不存在"));
|
||||
if (session.getExpiresAt().isBefore(LocalDateTime.now())) {
|
||||
throw new BusinessException("登录会话已过期");
|
||||
}
|
||||
User user = userRepository.findByOpenId(session.getOpenId()).orElseGet(User::new);
|
||||
user.setOpenId(session.getOpenId());
|
||||
user.setUnionId(session.getUnionId());
|
||||
user.setPhone(request.getPhone());
|
||||
user.touch();
|
||||
userRepository.save(user);
|
||||
return new LoginResponse(jwtService.issue(user.getId()), userService.toProfile(user));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
public class LoginSession {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String sessionKey;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String openId;
|
||||
|
||||
private String unionId;
|
||||
private LocalDateTime expiresAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public void setSessionKey(String sessionKey) {
|
||||
this.sessionKey = sessionKey;
|
||||
}
|
||||
|
||||
public String getOpenId() {
|
||||
return openId;
|
||||
}
|
||||
|
||||
public void setOpenId(String openId) {
|
||||
this.openId = openId;
|
||||
}
|
||||
|
||||
public String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
|
||||
public void setUnionId(String unionId) {
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
public LocalDateTime getExpiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
public void setExpiresAt(LocalDateTime expiresAt) {
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LoginSessionRepository extends JpaRepository<LoginSession, Long> {
|
||||
|
||||
Optional<LoginSession> findBySessionKey(String sessionKey);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
public class SmsCode {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String phone;
|
||||
private String code;
|
||||
private LocalDateTime expiresAt;
|
||||
private boolean used;
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public LocalDateTime getExpiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
public void setExpiresAt(LocalDateTime expiresAt) {
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
|
||||
public boolean isUsed() {
|
||||
return used;
|
||||
}
|
||||
|
||||
public void setUsed(boolean used) {
|
||||
this.used = used;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface SmsCodeRepository extends JpaRepository<SmsCode, Long> {
|
||||
|
||||
Optional<SmsCode> findTopByPhoneAndCodeAndUsedFalseOrderByExpiresAtDesc(String phone, String code);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Random;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class SmsService {
|
||||
|
||||
private final SmsCodeRepository smsCodeRepository;
|
||||
private final Random random = new Random();
|
||||
|
||||
public SmsService(SmsCodeRepository smsCodeRepository) {
|
||||
this.smsCodeRepository = smsCodeRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String send(String phone) {
|
||||
String code = String.format("%06d", random.nextInt(1_000_000));
|
||||
SmsCode smsCode = new SmsCode();
|
||||
smsCode.setPhone(phone);
|
||||
smsCode.setCode(code);
|
||||
smsCode.setExpiresAt(LocalDateTime.now().plusMinutes(5));
|
||||
smsCodeRepository.save(smsCode);
|
||||
return code;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void verify(String phone, String code) {
|
||||
SmsCode smsCode = smsCodeRepository.findTopByPhoneAndCodeAndUsedFalseOrderByExpiresAtDesc(phone, code)
|
||||
.orElseThrow(() -> new BusinessException("验证码不正确"));
|
||||
if (smsCode.getExpiresAt().isBefore(LocalDateTime.now())) {
|
||||
throw new BusinessException("验证码已过期");
|
||||
}
|
||||
smsCode.setUsed(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import com.hongzhen.hera.config.HeraProperties;
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@Component
|
||||
public class WechatClient {
|
||||
|
||||
private final HeraProperties properties;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public WechatClient(HeraProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public WxSession code2Session(String code) {
|
||||
if (properties.getWx().isMock()) {
|
||||
return new WxSession("mock-openid-" + sha1(code).substring(0, 12), null);
|
||||
}
|
||||
if (properties.getWx().getAppSecret() == null || properties.getWx().getAppSecret().trim().isEmpty()) {
|
||||
throw new BusinessException("未配置微信小程序 secret");
|
||||
}
|
||||
String url = UriComponentsBuilder
|
||||
.fromHttpUrl("https://api.weixin.qq.com/sns/jscode2session")
|
||||
.queryParam("appid", properties.getWx().getAppId())
|
||||
.queryParam("secret", properties.getWx().getAppSecret())
|
||||
.queryParam("js_code", code)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.toUriString();
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||
try {
|
||||
JsonNode body = objectMapper.readTree(response.getBody());
|
||||
if (body.has("errcode") && body.get("errcode").asInt() != 0) {
|
||||
throw new BusinessException("微信登录失败:" + body.path("errmsg").asText());
|
||||
}
|
||||
String openId = body.path("openid").asText();
|
||||
if (openId == null || openId.trim().isEmpty()) {
|
||||
throw new BusinessException("微信登录失败:未返回 openid");
|
||||
}
|
||||
String unionId = body.has("unionid") ? body.path("unionid").asText(null) : null;
|
||||
return new WxSession(openId, unionId);
|
||||
} catch (IOException exception) {
|
||||
throw new BusinessException("微信登录响应解析失败");
|
||||
}
|
||||
}
|
||||
|
||||
private String sha1(String value) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] bytes = digest.digest(value.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
builder.append(String.format("%02x", b));
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
public class WxSession {
|
||||
|
||||
private final String openId;
|
||||
private final String unionId;
|
||||
|
||||
public WxSession(String openId, String unionId) {
|
||||
this.openId = openId;
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
public String getOpenId() {
|
||||
return openId;
|
||||
}
|
||||
|
||||
public String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
public class ApiResponse<T> {
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
private final T data;
|
||||
|
||||
public ApiResponse(int code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> ok(T data) {
|
||||
return new ApiResponse<T>(0, "ok", data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> fail(String message) {
|
||||
return new ApiResponse<T>(400, message, null);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Void> handleBusiness(BusinessException exception) {
|
||||
return ApiResponse.fail(exception.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Void> handleValidation(Exception exception) {
|
||||
return ApiResponse.fail("参数不正确");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.hongzhen.hera.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "hera")
|
||||
public class HeraProperties {
|
||||
|
||||
private String jwtSecret;
|
||||
private final Wx wx = new Wx();
|
||||
private final Sms sms = new Sms();
|
||||
|
||||
public String getJwtSecret() {
|
||||
return jwtSecret;
|
||||
}
|
||||
|
||||
public void setJwtSecret(String jwtSecret) {
|
||||
this.jwtSecret = jwtSecret;
|
||||
}
|
||||
|
||||
public Wx getWx() {
|
||||
return wx;
|
||||
}
|
||||
|
||||
public Sms getSms() {
|
||||
return sms;
|
||||
}
|
||||
|
||||
public static class Wx {
|
||||
private String appId;
|
||||
private String appSecret;
|
||||
private boolean mock = true;
|
||||
|
||||
public String getAppId() {
|
||||
return appId;
|
||||
}
|
||||
|
||||
public void setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
public String getAppSecret() {
|
||||
return appSecret;
|
||||
}
|
||||
|
||||
public void setAppSecret(String appSecret) {
|
||||
this.appSecret = appSecret;
|
||||
}
|
||||
|
||||
public boolean isMock() {
|
||||
return mock;
|
||||
}
|
||||
|
||||
public void setMock(boolean mock) {
|
||||
this.mock = mock;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Sms {
|
||||
private boolean mock = true;
|
||||
|
||||
public boolean isMock() {
|
||||
return mock;
|
||||
}
|
||||
|
||||
public void setMock(boolean mock) {
|
||||
this.mock = mock;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.hongzhen.hera.config;
|
||||
|
||||
import com.hongzhen.hera.security.AuthInterceptor;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(HeraProperties.class)
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final AuthInterceptor authInterceptor;
|
||||
|
||||
public WebConfig(AuthInterceptor authInterceptor) {
|
||||
this.authInterceptor = authInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**").allowedMethods("*").allowedOrigins("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.addPathPatterns("/api/**")
|
||||
.excludePathPatterns("/api/auth/**", "/api/content/**", "/h2-console/**");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.hongzhen.hera.content;
|
||||
|
||||
import com.hongzhen.hera.common.ApiResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/content")
|
||||
public class ContentController {
|
||||
|
||||
@GetMapping("/agreement/{type}")
|
||||
public ApiResponse<Map<String, String>> agreement(@PathVariable String type) {
|
||||
String title = "privacy".equals(type) ? "隐私政策" : "用户协议";
|
||||
String body = "欢迎使用那吉。本页面内容为占位协议文本,请在正式上线前替换为经法务确认的完整条款。";
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
data.put("title", title);
|
||||
data.put("body", body);
|
||||
return ApiResponse.ok(data);
|
||||
}
|
||||
|
||||
@GetMapping("/follow")
|
||||
public ApiResponse<Map<String, String>> follow() {
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
data.put("title", "关注我们 一触即吉");
|
||||
data.put("redBookKeyword", "那小吉");
|
||||
data.put("videoKeyword", "那吉 nagiflow");
|
||||
data.put("brand", "那吉 NAGI FLOW");
|
||||
return ApiResponse.ok(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.hongzhen.hera.daily;
|
||||
|
||||
import com.hongzhen.hera.common.ApiResponse;
|
||||
import com.hongzhen.hera.daily.DailyDtos.DailyHomeResponse;
|
||||
import com.hongzhen.hera.daily.DailyDtos.PinCardResponse;
|
||||
import com.hongzhen.hera.security.CurrentUser;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/daily")
|
||||
public class DailyController {
|
||||
|
||||
private final DailyService dailyService;
|
||||
|
||||
public DailyController(DailyService dailyService) {
|
||||
this.dailyService = dailyService;
|
||||
}
|
||||
|
||||
@GetMapping("/home")
|
||||
public ApiResponse<DailyHomeResponse> home() {
|
||||
return ApiResponse.ok(dailyService.home(CurrentUser.id()));
|
||||
}
|
||||
|
||||
@PostMapping("/cards/{cardId}/pin")
|
||||
public ApiResponse<PinCardResponse> pin(@PathVariable String cardId) {
|
||||
return ApiResponse.ok(new PinCardResponse(dailyService.pin(CurrentUser.id(), cardId)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.hongzhen.hera.daily;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public class DailyDtos {
|
||||
|
||||
public static class DailyHomeResponse {
|
||||
private LocalDate date;
|
||||
private String lunarDate;
|
||||
private String weekName;
|
||||
private String fortuneLevel;
|
||||
private String greeting;
|
||||
private String dailySummary;
|
||||
private List<CalendarDay> calendarDays;
|
||||
private List<LuckyPanel> luckyPanels;
|
||||
private List<FortuneCard> cards;
|
||||
private List<LifeTip> lifeTips;
|
||||
private String pinnedCardId;
|
||||
|
||||
public DailyHomeResponse(LocalDate date, String lunarDate, String weekName, String fortuneLevel, String greeting,
|
||||
String dailySummary, List<CalendarDay> calendarDays, List<LuckyPanel> luckyPanels, List<FortuneCard> cards,
|
||||
List<LifeTip> lifeTips, String pinnedCardId) {
|
||||
this.date = date;
|
||||
this.lunarDate = lunarDate;
|
||||
this.weekName = weekName;
|
||||
this.fortuneLevel = fortuneLevel;
|
||||
this.greeting = greeting;
|
||||
this.dailySummary = dailySummary;
|
||||
this.calendarDays = calendarDays;
|
||||
this.luckyPanels = luckyPanels;
|
||||
this.cards = cards;
|
||||
this.lifeTips = lifeTips;
|
||||
this.pinnedCardId = pinnedCardId;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public String getLunarDate() {
|
||||
return lunarDate;
|
||||
}
|
||||
|
||||
public String getWeekName() {
|
||||
return weekName;
|
||||
}
|
||||
|
||||
public String getFortuneLevel() {
|
||||
return fortuneLevel;
|
||||
}
|
||||
|
||||
public String getGreeting() {
|
||||
return greeting;
|
||||
}
|
||||
|
||||
public String getDailySummary() {
|
||||
return dailySummary;
|
||||
}
|
||||
|
||||
public List<CalendarDay> getCalendarDays() {
|
||||
return calendarDays;
|
||||
}
|
||||
|
||||
public List<LuckyPanel> getLuckyPanels() {
|
||||
return luckyPanels;
|
||||
}
|
||||
|
||||
public List<FortuneCard> getCards() {
|
||||
return cards;
|
||||
}
|
||||
|
||||
public List<LifeTip> getLifeTips() {
|
||||
return lifeTips;
|
||||
}
|
||||
|
||||
public String getPinnedCardId() {
|
||||
return pinnedCardId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CalendarDay {
|
||||
private int day;
|
||||
private String weekName;
|
||||
private String label;
|
||||
|
||||
public CalendarDay(int day, String weekName, String label) {
|
||||
this.day = day;
|
||||
this.weekName = weekName;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public int getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public String getWeekName() {
|
||||
return weekName;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LuckyPanel {
|
||||
private String title;
|
||||
private String value;
|
||||
private String accent;
|
||||
|
||||
public LuckyPanel(String title, String value, String accent) {
|
||||
this.title = title;
|
||||
this.value = value;
|
||||
this.accent = accent;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getAccent() {
|
||||
return accent;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FortuneCard {
|
||||
private String id;
|
||||
private String title;
|
||||
private String category;
|
||||
private String categoryIcon;
|
||||
private String verdict;
|
||||
private String avoid;
|
||||
private String summary;
|
||||
private String detail;
|
||||
private String color;
|
||||
private String gradient;
|
||||
private String mascot;
|
||||
|
||||
public FortuneCard(String id, String title, String category, String categoryIcon, String verdict, String avoid,
|
||||
String summary, String detail, String color, String gradient, String mascot) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.category = category;
|
||||
this.categoryIcon = categoryIcon;
|
||||
this.verdict = verdict;
|
||||
this.avoid = avoid;
|
||||
this.summary = summary;
|
||||
this.detail = detail;
|
||||
this.color = color;
|
||||
this.gradient = gradient;
|
||||
this.mascot = mascot;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public String getCategoryIcon() {
|
||||
return categoryIcon;
|
||||
}
|
||||
|
||||
public String getVerdict() {
|
||||
return verdict;
|
||||
}
|
||||
|
||||
public String getAvoid() {
|
||||
return avoid;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public String getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public String getGradient() {
|
||||
return gradient;
|
||||
}
|
||||
|
||||
public String getMascot() {
|
||||
return mascot;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LifeTip {
|
||||
private String id;
|
||||
private String title;
|
||||
private String icon;
|
||||
private String content;
|
||||
private String color;
|
||||
private String mark;
|
||||
|
||||
public LifeTip(String id, String title, String icon, String content, String color, String mark) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.icon = icon;
|
||||
this.content = content;
|
||||
this.color = color;
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public String getMark() {
|
||||
return mark;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PinCardResponse {
|
||||
private String pinnedCardId;
|
||||
|
||||
public PinCardResponse(String pinnedCardId) {
|
||||
this.pinnedCardId = pinnedCardId;
|
||||
}
|
||||
|
||||
public String getPinnedCardId() {
|
||||
return pinnedCardId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.hongzhen.hera.daily;
|
||||
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import com.hongzhen.hera.daily.DailyDtos.CalendarDay;
|
||||
import com.hongzhen.hera.daily.DailyDtos.DailyHomeResponse;
|
||||
import com.hongzhen.hera.daily.DailyDtos.FortuneCard;
|
||||
import com.hongzhen.hera.daily.DailyDtos.LifeTip;
|
||||
import com.hongzhen.hera.daily.DailyDtos.LuckyPanel;
|
||||
import com.hongzhen.hera.user.User;
|
||||
import com.hongzhen.hera.user.UserRepository;
|
||||
import com.nlf.calendar.Solar;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class DailyService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public DailyService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
public DailyHomeResponse home(Long userId) {
|
||||
User user = user(userId);
|
||||
LocalDate today = LocalDate.now();
|
||||
return new DailyHomeResponse(
|
||||
today,
|
||||
lunarDate(today),
|
||||
weekName(today.getDayOfWeek()),
|
||||
"小逆",
|
||||
"晚上好," + user.getNickname() + "!",
|
||||
"今日宜静少思少虑,让身心在休整中蓄能。",
|
||||
calendarDays(today),
|
||||
luckyPanels(),
|
||||
cards(),
|
||||
lifeTips(),
|
||||
user.getPinnedDailyCardId()
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String pin(Long userId, String cardId) {
|
||||
boolean exists = false;
|
||||
for (FortuneCard card : cards()) {
|
||||
if (card.getId().equals(cardId)) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
throw new BusinessException("卡牌不存在");
|
||||
}
|
||||
User user = user(userId);
|
||||
String nextPinnedCardId = cardId.equals(user.getPinnedDailyCardId()) ? null : cardId;
|
||||
user.setPinnedDailyCardId(nextPinnedCardId);
|
||||
user.touch();
|
||||
return nextPinnedCardId;
|
||||
}
|
||||
|
||||
private User user(Long userId) {
|
||||
return userRepository.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
|
||||
}
|
||||
|
||||
private String lunarDate(LocalDate date) {
|
||||
return Solar.fromYmd(date.getYear(), date.getMonthValue(), date.getDayOfMonth()).getLunar().toString();
|
||||
}
|
||||
|
||||
private List<CalendarDay> calendarDays(LocalDate today) {
|
||||
LocalDate prev = today.minusDays(1);
|
||||
LocalDate next = today.plusDays(1);
|
||||
return Arrays.asList(
|
||||
new CalendarDay(prev.getDayOfMonth(), weekName(prev.getDayOfWeek()), "小吉"),
|
||||
new CalendarDay(today.getDayOfMonth(), weekName(today.getDayOfWeek()), "今日"),
|
||||
new CalendarDay(next.getDayOfMonth(), weekName(next.getDayOfWeek()), "平日")
|
||||
);
|
||||
}
|
||||
|
||||
private String weekName(DayOfWeek dayOfWeek) {
|
||||
String[] names = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
|
||||
return names[dayOfWeek.getValue() - 1];
|
||||
}
|
||||
|
||||
private List<LuckyPanel> luckyPanels() {
|
||||
return Arrays.asList(
|
||||
new LuckyPanel("吉色", "●\n宝象\n蓝牙白", "#34449a"),
|
||||
new LuckyPanel("吉向", "西\n北", "#a26711"),
|
||||
new LuckyPanel("吉时", "申 15点~17点\n丑 01点~03点", "#a26711"),
|
||||
new LuckyPanel("吉数", "1 / 9", "#a26711")
|
||||
);
|
||||
}
|
||||
|
||||
private List<FortuneCard> cards() {
|
||||
return Arrays.asList(
|
||||
new FortuneCard("travel", "逐风而行\n思绪开阔", "出行", "🍡", "宜", "忌",
|
||||
"今天去滨江步道,穿宽松卫衣,戴耳机听民谣,让风吹起头发,看江面波浪,烦心事都被吹走啦。",
|
||||
"出行宜慢行与看水。风动而心开,走到有水、有树、有开阔视野的地方,能把卡住的念头松开。若需远行,提前十五分钟出门,少赶路,多留白。",
|
||||
"#f05b45", "linear-gradient(180deg,#ff7569 0%,#fffaf0 76%)", "🏮"),
|
||||
new FortuneCard("wealth", "盲目扩张\n财气耗损", "财富", "🟠", "忌", "宜",
|
||||
"今天别一口气报多个付费课程或囤货,先推进手头正在做的事,把精力放在可控的小范围里更踏实。",
|
||||
"财富能量不宜过度分散,需聚焦核心,避免同时开启太多新尝试。今日翼宿值日,木气稍显。身弱之人本需金水滋养,然木旺则耗金生火,易令财源如春藤蔓生,看似繁茂却暗耗根基。",
|
||||
"#f17422", "linear-gradient(180deg,#ff9d49 0%,#fffaf0 76%)", "🪙"),
|
||||
new FortuneCard("life", "勿过度应酬\n多独处", "生活", "🍶", "忌", "宜",
|
||||
"推掉不必要的酒局,在家煮一碗热汤面,看一部喜欢的纪录片,享受独处的安静时光。",
|
||||
"生活宜收束边界,少用热闹消耗自己。把房间收拾出一块干净角落,吃温热简单的食物,留给身体一点安静,今日的气会慢慢回到你身上。",
|
||||
"#f5b700", "linear-gradient(180deg,#ffd91f 0%,#fffaf0 76%)", "🍶"),
|
||||
new FortuneCard("study", "秋水沉静\n深度思考问题", "学业", "🎋", "宜", "忌",
|
||||
"找个安静的咖啡馆,点一杯热拿铁,对着一个知识点写一篇小感悟,秋日的沉静会让思考更有深度。",
|
||||
"翼宿主文昌,水势沉静时。今日水星得势,怡应身弱之需,金水相生,思绪如秋水澄澈。木火土三忌退避,杂念不生,正宜深潜学海。金为水源,得此加持,逻辑脉络自明;水主智识,沉浸其中,难题可化涟漪。",
|
||||
"#20b98f", "linear-gradient(180deg,#29c89f 0%,#fffaf0 76%)", "🎍"),
|
||||
new FortuneCard("career", "情绪躁动\n避免冒进", "事业", "🌬", "忌", "宜",
|
||||
"今天提交方案前,先找信任的同事帮忙过一遍细节,或者把方案放半小时再看一遍,确保没有疏漏后再行动。",
|
||||
"今日容易冲动行事,适合稳扎稳打,不要贸然提交不成熟的方案。火气炽盛,于身弱喜金水者而言,更需警惕言语冲动引燃事端。宜效法秋水之静,涵养金气之锐。",
|
||||
"#5cc4c8", "linear-gradient(180deg,#76dcd5 0%,#fffaf0 76%)", "☁"),
|
||||
new FortuneCard("health", "稳守心神\n忌急躁多动", "健康", "💧", "忌", "宜",
|
||||
"穿浅绿纯棉T恤,待在通风书房,喝杯加薄荷叶的温水,每隔1小时看窗外5分钟,避免情绪急躁。",
|
||||
"健康宜清润、缓行、少燥。今日心火易浮,若一直急着处理细碎事务,会让身体更累。适合补水、通风、放慢脚步,晚上尽量早点休息。",
|
||||
"#42aee8", "linear-gradient(180deg,#8ad8ff 0%,#fffaf0 76%)", "🪢"),
|
||||
new FortuneCard("social", "深夜畅聊\n知己深谈", "社交", "🦊", "宜", "忌",
|
||||
"适合和知己深度对话,解开心里的小疙瘩。",
|
||||
"今日水气氤氲,润泽身弱之局。金水相生,恰逢知己深谈之机,如清泉涤尘,可解心结。翼宿主沟通,暗合水德流动之性,宜借言语之柔波,疏泄郁结。",
|
||||
"#c879e8", "linear-gradient(180deg,#e8a4ff 0%,#fffaf0 76%)", "🦊"),
|
||||
new FortuneCard("love", "长势放缓\n忌过度索取", "情感", "🌸", "忌", "宜",
|
||||
"今日情感节奏偏缓,别给关系施加太多期待。",
|
||||
"今日翼宿值守,木气升腾。身弱之人逢木旺,如藤蔓缠枝,易生过度索求之心。金水为喜而难制木,情感如溪流遇石,节奏自当放缓。莫强求炽热,宜守静观澜。",
|
||||
"#f06aa6", "linear-gradient(180deg,#ff7fbc 0%,#fffaf0 76%)", "🌸")
|
||||
);
|
||||
}
|
||||
|
||||
private List<LifeTip> lifeTips() {
|
||||
return Arrays.asList(
|
||||
new LifeTip("travel", "出行", "🍡", "今天去滨江步道,穿宽松卫衣,戴耳机听民谣,让风吹起头发,看江面波浪,烦心事都被吹走啦。", "#ff7569", "灯笼"),
|
||||
new LifeTip("wealth", "财富", "🟠", "今天别一口气报多个付费课程或囤货,先推进手头正在做的事,把精力放在可控的小范围里更踏实。", "#ff9d49", "铜钱"),
|
||||
new LifeTip("life", "生活", "🍶", "推掉不必要的酒局,在家煮一碗热汤面,看一部喜欢的纪录片,享受独处的安静时光。", "#ffe75a", "葫芦"),
|
||||
new LifeTip("study", "学业", "🎋", "找个安静的咖啡馆,点一杯热拿铁,对着一个知识点写一篇小感悟,秋日的沉静会让思考更有深度。", "#96efc7", "书卷"),
|
||||
new LifeTip("career", "事业", "🌬", "今天提交方案前,先找信任的同事帮忙过一遍细节,或者把方案放半小时再看一遍,确保没有疏漏后再行动。", "#70e4df", "祥云"),
|
||||
new LifeTip("health", "健康", "💧", "穿浅绿纯棉T恤,待在通风书房,喝杯加薄荷叶的温水,每隔1小时看窗外5分钟,避免情绪急躁。", "#86d4ff", "绳结"),
|
||||
new LifeTip("social", "社交", "🦊", "晚上和兄弟约在楼下的烧烤摊,点几串烤串,说说最近工作上的压力,不用掩饰脆弱,懂你的人自然会懂。", "#e7a3ff", "狐狸"),
|
||||
new LifeTip("love", "情感", "🌸", "别追问对方为什么不秒回消息,可以自己插一瓶小雏菊在书桌,读一本短篇散文,给彼此留一点呼吸空间。", "#ff89c4", "花")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package com.hongzhen.hera.fortune;
|
||||
|
||||
import com.hongzhen.hera.user.ProfileDtos.ElementReport;
|
||||
import com.hongzhen.hera.user.ProfileDtos.ElementScore;
|
||||
import com.nlf.calendar.EightChar;
|
||||
import com.nlf.calendar.Solar;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class FortuneService {
|
||||
|
||||
private static final List<String> ELEMENTS = Arrays.asList("木", "火", "土", "金", "水");
|
||||
private static final Map<String, String> STEM_ELEMENTS = new HashMap<String, String>();
|
||||
private static final Map<String, String> BRANCH_ELEMENTS = new HashMap<String, String>();
|
||||
private static final Map<String, Integer> HOUR_STARTS = new HashMap<String, Integer>();
|
||||
private static final Map<String, String> COLORS = new HashMap<String, String>();
|
||||
|
||||
static {
|
||||
STEM_ELEMENTS.put("甲", "木");
|
||||
STEM_ELEMENTS.put("乙", "木");
|
||||
STEM_ELEMENTS.put("丙", "火");
|
||||
STEM_ELEMENTS.put("丁", "火");
|
||||
STEM_ELEMENTS.put("戊", "土");
|
||||
STEM_ELEMENTS.put("己", "土");
|
||||
STEM_ELEMENTS.put("庚", "金");
|
||||
STEM_ELEMENTS.put("辛", "金");
|
||||
STEM_ELEMENTS.put("壬", "水");
|
||||
STEM_ELEMENTS.put("癸", "水");
|
||||
|
||||
BRANCH_ELEMENTS.put("寅", "木");
|
||||
BRANCH_ELEMENTS.put("卯", "木");
|
||||
BRANCH_ELEMENTS.put("巳", "火");
|
||||
BRANCH_ELEMENTS.put("午", "火");
|
||||
BRANCH_ELEMENTS.put("辰", "土");
|
||||
BRANCH_ELEMENTS.put("戌", "土");
|
||||
BRANCH_ELEMENTS.put("丑", "土");
|
||||
BRANCH_ELEMENTS.put("未", "土");
|
||||
BRANCH_ELEMENTS.put("申", "金");
|
||||
BRANCH_ELEMENTS.put("酉", "金");
|
||||
BRANCH_ELEMENTS.put("亥", "水");
|
||||
BRANCH_ELEMENTS.put("子", "水");
|
||||
|
||||
HOUR_STARTS.put("子时", 0);
|
||||
HOUR_STARTS.put("丑时", 1);
|
||||
HOUR_STARTS.put("寅时", 3);
|
||||
HOUR_STARTS.put("卯时", 5);
|
||||
HOUR_STARTS.put("辰时", 7);
|
||||
HOUR_STARTS.put("巳时", 9);
|
||||
HOUR_STARTS.put("午时", 11);
|
||||
HOUR_STARTS.put("未时", 13);
|
||||
HOUR_STARTS.put("申时", 15);
|
||||
HOUR_STARTS.put("酉时", 17);
|
||||
HOUR_STARTS.put("戌时", 19);
|
||||
HOUR_STARTS.put("亥时", 21);
|
||||
|
||||
COLORS.put("木", "#53d83c");
|
||||
COLORS.put("火", "#ff4b88");
|
||||
COLORS.put("土", "#b98224");
|
||||
COLORS.put("金", "#ffd316");
|
||||
COLORS.put("水", "#13cce9");
|
||||
}
|
||||
|
||||
public ElementReport calculate(LocalDate birthday, String birthHour) {
|
||||
int hour = HOUR_STARTS.containsKey(birthHour) ? HOUR_STARTS.get(birthHour) : 0;
|
||||
EightChar eightChar = Solar.fromYmdHms(
|
||||
birthday.getYear(),
|
||||
birthday.getMonthValue(),
|
||||
birthday.getDayOfMonth(),
|
||||
hour,
|
||||
0,
|
||||
0
|
||||
).getLunar().getEightChar();
|
||||
|
||||
Map<String, Integer> weights = initialWeights();
|
||||
addPillar(weights, eightChar.getYear(), 10, 10);
|
||||
addPillar(weights, eightChar.getMonth(), 10, 18);
|
||||
addPillar(weights, eightChar.getDay(), 12, 10);
|
||||
addPillar(weights, eightChar.getTime(), 10, 10);
|
||||
|
||||
List<ElementScore> scores = toScores(weights);
|
||||
String dayMaster = eightChar.getDayGan();
|
||||
String dayMasterElement = elementOfStem(dayMaster);
|
||||
String favorable = favorableElements(dayMasterElement, weights);
|
||||
String unfavorable = unfavorableElements(favorable);
|
||||
|
||||
String year = eightChar.getYear();
|
||||
String month = eightChar.getMonth();
|
||||
String day = eightChar.getDay();
|
||||
String time = eightChar.getTime();
|
||||
return new ElementReport(scores, favorable, unfavorable, dayMaster, year + " " + month + " " + day + " " + time,
|
||||
year, month, day, time);
|
||||
}
|
||||
|
||||
private Map<String, Integer> initialWeights() {
|
||||
Map<String, Integer> weights = new HashMap<String, Integer>();
|
||||
for (String element : ELEMENTS) {
|
||||
weights.put(element, 0);
|
||||
}
|
||||
return weights;
|
||||
}
|
||||
|
||||
private void addPillar(Map<String, Integer> weights, String pillar, int stemWeight, int branchWeight) {
|
||||
if (pillar == null || pillar.length() < 2) {
|
||||
return;
|
||||
}
|
||||
addWeight(weights, STEM_ELEMENTS.get(pillar.substring(0, 1)), stemWeight);
|
||||
addWeight(weights, BRANCH_ELEMENTS.get(pillar.substring(1, 2)), branchWeight);
|
||||
}
|
||||
|
||||
private void addWeight(Map<String, Integer> weights, String element, int weight) {
|
||||
if (element != null) {
|
||||
weights.put(element, weights.get(element) + weight);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ElementScore> toScores(Map<String, Integer> weights) {
|
||||
int total = 0;
|
||||
for (Integer weight : weights.values()) {
|
||||
total += weight;
|
||||
}
|
||||
List<ElementScore> scores = new ArrayList<ElementScore>();
|
||||
for (String element : ELEMENTS) {
|
||||
int value = total == 0 ? 0 : Math.round(weights.get(element) * 100f / total);
|
||||
scores.add(new ElementScore(element, COLORS.get(element), value));
|
||||
}
|
||||
return scores;
|
||||
}
|
||||
|
||||
private String favorableElements(String dayMaster, Map<String, Integer> weights) {
|
||||
String supporter = produces(dayMaster);
|
||||
int selfAndSupport = weights.get(dayMaster) + weights.get(supporter);
|
||||
if (selfAndSupport < 45) {
|
||||
return supporter + dayMaster;
|
||||
}
|
||||
return producedBy(dayMaster) + controlledBy(dayMaster);
|
||||
}
|
||||
|
||||
private String unfavorableElements(String favorable) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String element : ELEMENTS) {
|
||||
if (!favorable.contains(element)) {
|
||||
builder.append(element);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String elementOfStem(String stem) {
|
||||
String element = STEM_ELEMENTS.get(stem);
|
||||
return element == null ? "土" : element;
|
||||
}
|
||||
|
||||
private String produces(String element) {
|
||||
if ("木".equals(element)) {
|
||||
return "水";
|
||||
}
|
||||
if ("火".equals(element)) {
|
||||
return "木";
|
||||
}
|
||||
if ("土".equals(element)) {
|
||||
return "火";
|
||||
}
|
||||
if ("金".equals(element)) {
|
||||
return "土";
|
||||
}
|
||||
return "金";
|
||||
}
|
||||
|
||||
private String producedBy(String element) {
|
||||
if ("木".equals(element)) {
|
||||
return "火";
|
||||
}
|
||||
if ("火".equals(element)) {
|
||||
return "土";
|
||||
}
|
||||
if ("土".equals(element)) {
|
||||
return "金";
|
||||
}
|
||||
if ("金".equals(element)) {
|
||||
return "水";
|
||||
}
|
||||
return "木";
|
||||
}
|
||||
|
||||
private String controlledBy(String element) {
|
||||
if ("木".equals(element)) {
|
||||
return "土";
|
||||
}
|
||||
if ("火".equals(element)) {
|
||||
return "金";
|
||||
}
|
||||
if ("土".equals(element)) {
|
||||
return "水";
|
||||
}
|
||||
if ("金".equals(element)) {
|
||||
return "木";
|
||||
}
|
||||
return "火";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.hongzhen.hera.points;
|
||||
|
||||
import com.hongzhen.hera.common.ApiResponse;
|
||||
import com.hongzhen.hera.security.CurrentUser;
|
||||
import com.hongzhen.hera.user.UserService;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/points")
|
||||
public class PointsController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public PointsController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping("/exchange-status")
|
||||
public ApiResponse<Map<String, Object>> exchangeStatus() {
|
||||
int points = userService.get(CurrentUser.id()).getPoints();
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
data.put("points", points);
|
||||
data.put("available", false);
|
||||
data.put("message", "你当前拥有 " + points + " 吉分,兑换功能即将开放,敬请期待!");
|
||||
return ApiResponse.ok(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.hongzhen.hera.security;
|
||||
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final JwtService jwtService;
|
||||
|
||||
public AuthInterceptor(JwtService jwtService) {
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String auth = request.getHeader("Authorization");
|
||||
if (auth == null || !auth.startsWith("Bearer ")) {
|
||||
throw new BusinessException("请先登录");
|
||||
}
|
||||
CurrentUser.set(jwtService.parseUserId(auth.substring(7)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
CurrentUser.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.hongzhen.hera.security;
|
||||
|
||||
public final class CurrentUser {
|
||||
|
||||
private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();
|
||||
|
||||
private CurrentUser() {
|
||||
}
|
||||
|
||||
public static void set(Long userId) {
|
||||
USER_ID.set(userId);
|
||||
}
|
||||
|
||||
public static Long id() {
|
||||
return USER_ID.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
USER_ID.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.hongzhen.hera.security;
|
||||
|
||||
import com.hongzhen.hera.config.HeraProperties;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
public JwtService(HeraProperties properties) {
|
||||
this.key = properties.getJwtSecret().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public String issue(Long userId) {
|
||||
Instant now = Instant.now();
|
||||
return Jwts.builder()
|
||||
.setSubject(String.valueOf(userId))
|
||||
.setIssuedAt(Date.from(now))
|
||||
.setExpiration(Date.from(now.plusSeconds(60L * 60L * 24L * 30L)))
|
||||
.signWith(SignatureAlgorithm.HS256, key)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Long parseUserId(String token) {
|
||||
return Long.valueOf(Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getSubject());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class ProfileDtos {
|
||||
|
||||
public static class ProfileResponse {
|
||||
private Long id;
|
||||
private String nickname;
|
||||
private String phone;
|
||||
private String gender;
|
||||
private LocalDate birthday;
|
||||
private String birthHour;
|
||||
private String province;
|
||||
private String city;
|
||||
private String district;
|
||||
private int points;
|
||||
private int remainingEdits;
|
||||
private ElementReport elementReport;
|
||||
|
||||
public ProfileResponse(Long id, String nickname, String phone, String gender, LocalDate birthday, String birthHour,
|
||||
String province, String city, String district, int points, int remainingEdits, ElementReport elementReport) {
|
||||
this.id = id;
|
||||
this.nickname = nickname;
|
||||
this.phone = phone;
|
||||
this.gender = gender;
|
||||
this.birthday = birthday;
|
||||
this.birthHour = birthHour;
|
||||
this.province = province;
|
||||
this.city = city;
|
||||
this.district = district;
|
||||
this.points = points;
|
||||
this.remainingEdits = remainingEdits;
|
||||
this.elementReport = elementReport;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public LocalDate getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
public String getBirthHour() {
|
||||
return birthHour;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public String getDistrict() {
|
||||
return district;
|
||||
}
|
||||
|
||||
public int getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public int getRemainingEdits() {
|
||||
return remainingEdits;
|
||||
}
|
||||
|
||||
public ElementReport getElementReport() {
|
||||
return elementReport;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ElementReport {
|
||||
private List<ElementScore> scores;
|
||||
private String favorable;
|
||||
private String unfavorable;
|
||||
private String dayMaster;
|
||||
private String bazi;
|
||||
private String yearPillar;
|
||||
private String monthPillar;
|
||||
private String dayPillar;
|
||||
private String timePillar;
|
||||
|
||||
public ElementReport(List<ElementScore> scores, String favorable, String unfavorable, String dayMaster,
|
||||
String bazi, String yearPillar, String monthPillar, String dayPillar, String timePillar) {
|
||||
this.scores = scores;
|
||||
this.favorable = favorable;
|
||||
this.unfavorable = unfavorable;
|
||||
this.dayMaster = dayMaster;
|
||||
this.bazi = bazi;
|
||||
this.yearPillar = yearPillar;
|
||||
this.monthPillar = monthPillar;
|
||||
this.dayPillar = dayPillar;
|
||||
this.timePillar = timePillar;
|
||||
}
|
||||
|
||||
public List<ElementScore> getScores() {
|
||||
return scores;
|
||||
}
|
||||
|
||||
public String getFavorable() {
|
||||
return favorable;
|
||||
}
|
||||
|
||||
public String getUnfavorable() {
|
||||
return unfavorable;
|
||||
}
|
||||
|
||||
public String getDayMaster() {
|
||||
return dayMaster;
|
||||
}
|
||||
|
||||
public String getBazi() {
|
||||
return bazi;
|
||||
}
|
||||
|
||||
public String getYearPillar() {
|
||||
return yearPillar;
|
||||
}
|
||||
|
||||
public String getMonthPillar() {
|
||||
return monthPillar;
|
||||
}
|
||||
|
||||
public String getDayPillar() {
|
||||
return dayPillar;
|
||||
}
|
||||
|
||||
public String getTimePillar() {
|
||||
return timePillar;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ElementScore {
|
||||
private String name;
|
||||
private String color;
|
||||
private int value;
|
||||
|
||||
public ElementScore(String name, String color, int value) {
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateProfileRequest {
|
||||
@NotBlank
|
||||
private String nickname;
|
||||
@NotBlank
|
||||
private String gender;
|
||||
@NotNull
|
||||
private LocalDate birthday;
|
||||
@NotBlank
|
||||
private String birthHour;
|
||||
@NotBlank
|
||||
private String province;
|
||||
@NotBlank
|
||||
private String city;
|
||||
@NotBlank
|
||||
private String district;
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public LocalDate getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
public String getBirthHour() {
|
||||
return birthHour;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public String getDistrict() {
|
||||
return district;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "hera_user")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String openId;
|
||||
|
||||
private String unionId;
|
||||
private String phone;
|
||||
private String nickname = "道道3338";
|
||||
private String gender = "男";
|
||||
private LocalDate birthday = LocalDate.of(2008, 12, 28);
|
||||
private String birthHour = "寅时";
|
||||
private String province = "河南省";
|
||||
private String city = "洛阳市";
|
||||
private String district = "老城区";
|
||||
private int points = 0;
|
||||
private String pinnedDailyCardId;
|
||||
private int profileEditCount = 0;
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
private LocalDateTime updatedAt = LocalDateTime.now();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getOpenId() {
|
||||
return openId;
|
||||
}
|
||||
|
||||
public void setOpenId(String openId) {
|
||||
this.openId = openId;
|
||||
}
|
||||
|
||||
public String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
|
||||
public void setUnionId(String unionId) {
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(String gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public LocalDate getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
public void setBirthday(LocalDate birthday) {
|
||||
this.birthday = birthday;
|
||||
}
|
||||
|
||||
public String getBirthHour() {
|
||||
return birthHour;
|
||||
}
|
||||
|
||||
public void setBirthHour(String birthHour) {
|
||||
this.birthHour = birthHour;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getDistrict() {
|
||||
return district;
|
||||
}
|
||||
|
||||
public void setDistrict(String district) {
|
||||
this.district = district;
|
||||
}
|
||||
|
||||
public int getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void setPoints(int points) {
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public String getPinnedDailyCardId() {
|
||||
return pinnedDailyCardId;
|
||||
}
|
||||
|
||||
public void setPinnedDailyCardId(String pinnedDailyCardId) {
|
||||
this.pinnedDailyCardId = pinnedDailyCardId;
|
||||
}
|
||||
|
||||
public int getProfileEditCount() {
|
||||
return profileEditCount;
|
||||
}
|
||||
|
||||
public void setProfileEditCount(int profileEditCount) {
|
||||
this.profileEditCount = profileEditCount;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void touch() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import com.hongzhen.hera.common.ApiResponse;
|
||||
import com.hongzhen.hera.security.CurrentUser;
|
||||
import com.hongzhen.hera.user.ProfileDtos.ProfileResponse;
|
||||
import com.hongzhen.hera.user.ProfileDtos.UpdateProfileRequest;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public ApiResponse<ProfileResponse> profile() {
|
||||
return ApiResponse.ok(userService.profile(CurrentUser.id()));
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<ProfileResponse> update(@Valid @RequestBody UpdateProfileRequest request) {
|
||||
return ApiResponse.ok(userService.updateProfile(CurrentUser.id(), request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
Optional<User> findByOpenId(String openId);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import com.hongzhen.hera.fortune.FortuneService;
|
||||
import com.hongzhen.hera.user.ProfileDtos.ElementReport;
|
||||
import com.hongzhen.hera.user.ProfileDtos.ProfileResponse;
|
||||
import com.hongzhen.hera.user.ProfileDtos.UpdateProfileRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final FortuneService fortuneService;
|
||||
|
||||
public UserService(UserRepository userRepository, FortuneService fortuneService) {
|
||||
this.userRepository = userRepository;
|
||||
this.fortuneService = fortuneService;
|
||||
}
|
||||
|
||||
public User get(Long userId) {
|
||||
return userRepository.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
|
||||
}
|
||||
|
||||
public ProfileResponse profile(Long userId) {
|
||||
return toProfile(get(userId));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ProfileResponse updateProfile(Long userId, UpdateProfileRequest request) {
|
||||
User user = get(userId);
|
||||
if (user.getProfileEditCount() >= 5) {
|
||||
throw new BusinessException("半年内只能修改五次哦");
|
||||
}
|
||||
user.setNickname(request.getNickname());
|
||||
user.setGender(request.getGender());
|
||||
user.setBirthday(request.getBirthday());
|
||||
user.setBirthHour(request.getBirthHour());
|
||||
user.setProvince(request.getProvince());
|
||||
user.setCity(request.getCity());
|
||||
user.setDistrict(request.getDistrict());
|
||||
user.setProfileEditCount(user.getProfileEditCount() + 1);
|
||||
user.touch();
|
||||
return toProfile(user);
|
||||
}
|
||||
|
||||
public ProfileResponse toProfile(User user) {
|
||||
ElementReport report = fortuneService.calculate(user.getBirthday(), user.getBirthHour());
|
||||
return new ProfileResponse(
|
||||
user.getId(),
|
||||
user.getNickname(),
|
||||
user.getPhone(),
|
||||
user.getGender(),
|
||||
user.getBirthday(),
|
||||
user.getBirthHour(),
|
||||
user.getProvince(),
|
||||
user.getCity(),
|
||||
user.getDistrict(),
|
||||
user.getPoints(),
|
||||
Math.max(0, 5 - user.getProfileEditCount()),
|
||||
report
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: hera
|
||||
datasource:
|
||||
url: jdbc:h2:mem:hera;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1
|
||||
username: sa
|
||||
password:
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
|
||||
hera:
|
||||
jwt-secret: "replace-this-development-secret-replace-this-development-secret"
|
||||
wx:
|
||||
app-id: "${WECHAT_APP_ID:wxa76a2ef9e8ba612e}"
|
||||
app-secret: "${WECHAT_APP_SECRET:}"
|
||||
mock: false
|
||||
sms:
|
||||
mock: true
|
||||
Reference in new issue
Block a user