Initial commit: Monisuo - 虚拟货币模拟交易平台
功能模块: - 用户注册/登录/KYC - 资金账户/交易账户 - 实时行情/币种管理 - 即时交易/充提审核 - 管理后台 技术栈: - 后端: SpringBoot 2.2.4 + MyBatis Plus - 前端: uni-app x (Vue3 + UTS) - 数据库: MySQL Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
23
src/main/java/com/it/rattan/SpcCloudApplication.java
Normal file
23
src/main/java/com/it/rattan/SpcCloudApplication.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.it.rattan;
|
||||
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@ServletComponentScan(basePackages ={"com.it.rattan"})
|
||||
@ComponentScan(basePackages ={"com.it.rattan"})
|
||||
/*@EnableAsync
|
||||
@EnableAspectJAutoProxy*/
|
||||
public class SpcCloudApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpcCloudApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
62
src/main/java/com/it/rattan/SwaggerConfig.java
Normal file
62
src/main/java/com/it/rattan/SwaggerConfig.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.it.rattan;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.ParameterBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.schema.ModelRef;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public Docket api() {
|
||||
return new Docket(DocumentationType.SWAGGER_2).
|
||||
useDefaultResponseMessages(false)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.any())
|
||||
.paths(PathSelectors.regex("^(?!auth).*$"))
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts())
|
||||
;
|
||||
}
|
||||
private List<ApiKey> securitySchemes() {
|
||||
List<ApiKey> apiKey = new ArrayList();
|
||||
apiKey.add(new ApiKey("Authorization", "Authorization", "header"));
|
||||
return apiKey;
|
||||
}
|
||||
private List<SecurityContext> securityContexts() {
|
||||
List<SecurityContext> securityContexts = new ArrayList();
|
||||
SecurityContext securityContext = SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.forPaths(PathSelectors.regex("^(?!auth).*$"))
|
||||
.build();
|
||||
securityContexts.add(securityContext);
|
||||
return securityContexts;
|
||||
}
|
||||
List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||
authorizationScopes[0] = authorizationScope;
|
||||
List<SecurityReference> securityReferences = new ArrayList();
|
||||
SecurityReference securityContext = new SecurityReference("Authorization", authorizationScopes);
|
||||
securityReferences.add(securityContext);
|
||||
return securityReferences;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
13
src/main/java/com/it/rattan/config/RattanClientConfig.java
Normal file
13
src/main/java/com/it/rattan/config/RattanClientConfig.java
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
package com.it.rattan.config;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
public class RattanClientConfig {
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
40
src/main/java/com/it/rattan/config/WebConfig.java
Normal file
40
src/main/java/com/it/rattan/config/WebConfig.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.it.rattan.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
// 允许所有来源
|
||||
config.addAllowedOrigin("*");
|
||||
// 允许所有请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 允许所有请求方法
|
||||
config.addAllowedMethod("*");
|
||||
// 允许携带凭证
|
||||
config.setAllowCredentials(true);
|
||||
// 预检请求缓存时间
|
||||
config.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
9
src/main/java/com/it/rattan/enums/RattanMark.java
Normal file
9
src/main/java/com/it/rattan/enums/RattanMark.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.it.rattan.enums;
|
||||
|
||||
public interface RattanMark {
|
||||
|
||||
String EMPTY = "";
|
||||
|
||||
String NULL = null;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.it.rattan.exception;
|
||||
|
||||
import com.it.rattan.rpc.RattanResponse;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobExceptionHandler {
|
||||
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
@ResponseBody
|
||||
public Object handle(final Exception e){
|
||||
return RattanResponse.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
21
src/main/java/com/it/rattan/exception/LoginException.java
Normal file
21
src/main/java/com/it/rattan/exception/LoginException.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.it.rattan.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class LoginException extends RuntimeException {
|
||||
|
||||
private static final String CODE = "401";
|
||||
|
||||
private String msg;
|
||||
|
||||
private String code;
|
||||
|
||||
public LoginException(String msg){
|
||||
super(msg);
|
||||
this.code = CODE;
|
||||
}
|
||||
|
||||
}
|
||||
103
src/main/java/com/it/rattan/intceptor/MySqlInterceptor.java
Normal file
103
src/main/java/com/it/rattan/intceptor/MySqlInterceptor.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.it.rattan.intceptor;
|
||||
|
||||
import com.ejlchina.searcher.SearchSql;
|
||||
import com.ejlchina.searcher.SqlInterceptor;
|
||||
import com.ejlchina.searcher.util.StringUtils;
|
||||
import com.it.rattan.SpcCloudApplication;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class MySqlInterceptor implements SqlInterceptor {
|
||||
@Override
|
||||
public <T> SearchSql<T> intercept(SearchSql<T> searchSql, Map<String, Object> paraMap) {
|
||||
if (searchSql.isShouldQueryList()) {
|
||||
String listSql = searchSql.getListSqlString();
|
||||
String countSql = searchSql.getClusterSqlString();
|
||||
String prodSearchKey = (String) paraMap.get("prodName-prodModel");
|
||||
String orderByStr = "";
|
||||
if(listSql.toUpperCase().contains(" ORDER BY ")){
|
||||
orderByStr = listSql.substring(listSql.toUpperCase().indexOf("ORDER BY"));
|
||||
orderByStr = orderByStr.substring(0,orderByStr.indexOf("limit"));
|
||||
}
|
||||
if(StringUtils.isNotBlank(prodSearchKey)){
|
||||
listSql = listSql.replace(orderByStr,"");
|
||||
if(listSql.toUpperCase().contains(" WHERE ")){
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "and (t.prod_name like '%"+prodSearchKey+"%' or pd.prod_Model like '%"+prodSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit") );
|
||||
countSql = countSql + "and (t.prod_name like '%"+prodSearchKey+"%' or pd.prod_Model like '%"+prodSearchKey+"%')";
|
||||
}else{
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "where (t.prod_name like '%"+prodSearchKey+"%' or pd.prod_Model like '%"+prodSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit"));
|
||||
countSql = countSql + " where (t.prod_name like '%"+prodSearchKey+"%' or pd.prod_Model like '%"+prodSearchKey+"%')";
|
||||
}
|
||||
}
|
||||
|
||||
String quotationSearchKey = (String) paraMap.get("projName-quotationNum");
|
||||
|
||||
if(StringUtils.isNotBlank(quotationSearchKey)){
|
||||
listSql = listSql.replace(orderByStr,"");
|
||||
if(listSql.toUpperCase().contains(" WHERE ")){
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "and (t.proj_Name like '%"+quotationSearchKey+"%' or t.quotation_Num like '%"+quotationSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit") );
|
||||
countSql = countSql + "and (t.proj_Name like '%"+quotationSearchKey+"%' or t.quotation_Num like '%"+quotationSearchKey+"%')";
|
||||
}else{
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "where (t.proj_Name like '%"+quotationSearchKey+"%' or t.quotation_Num like '%"+quotationSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit"));
|
||||
countSql = countSql + " where (t.proj_Name like '%"+quotationSearchKey+"%' or t.quotation_Num like '%"+quotationSearchKey+"%')";
|
||||
}
|
||||
}
|
||||
|
||||
String contractSearchKey = (String) paraMap.get("contractNum-contractName");
|
||||
|
||||
if(StringUtils.isNotBlank(contractSearchKey)){
|
||||
listSql = listSql.replace(orderByStr,"");
|
||||
if(listSql.toUpperCase().contains(" WHERE ")){
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "and (t.contract_Num like '%"+contractSearchKey+"%' or t.contract_Name like '%"+contractSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit") );
|
||||
countSql = countSql + "and (t.contract_Num like '%"+contractSearchKey+"%' or t.contract_Name like '%"+contractSearchKey+"%')";
|
||||
}else{
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "where (t.contract_Num like '%"+contractSearchKey+"%' or t.contract_Name like '%"+contractSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit"));
|
||||
countSql = countSql + " where (t.contract_Num like '%"+contractSearchKey+"%' or t.contract_Name like '%"+contractSearchKey+"%')";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
String userSearchKey = (String) paraMap.get("userAccount-userName");
|
||||
if(StringUtils.isNotBlank(userSearchKey)){
|
||||
listSql = listSql.replace(orderByStr,"");
|
||||
if(listSql.toUpperCase().contains(" WHERE ")){
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "and (u.user_Account like '%"+userSearchKey+"%' or u.user_Name like '%"+userSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit") );
|
||||
countSql = countSql + "and (u.user_Account like '%"+userSearchKey+"%' or u.user_Name like '%"+userSearchKey+"%')";
|
||||
}else{
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "where (u.user_Account like '%"+userSearchKey+"%' or u.user_Name like '%"+userSearchKey+"%') " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit"));
|
||||
countSql = countSql + " where (u.user_Account like '%"+userSearchKey+"%' or u.user_Name like '%"+userSearchKey+"%')";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
String searchDay = (String) paraMap.get("searchDay");
|
||||
//YEARWEEK( FROM_UNIXTIME( `created_at`, "%Y-%m-%d %H:%i:%s" ) ,1) = YEARWEEK( now(),1 )
|
||||
if(StringUtils.isNotBlank(searchDay) && ("today".equals(searchDay) || "week".equals(searchDay))){
|
||||
listSql = listSql.replace(orderByStr,"");
|
||||
if(listSql.toUpperCase().contains(" WHERE ")){
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "and ("+("today".equals(searchDay) ? "to_days(t.create_time) = to_days(now()) " : "YEARWEEK(date_format(t.create_time,'%Y-%m-%d'),1)=YEARWEEK(now(),1)")+") " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit") );
|
||||
countSql = countSql + "and ("+("today".equals(searchDay) ? "to_days(t.create_time) = to_days(now()) " : "YEARWEEK(date_format(t.create_time,'%Y-%m-%d'),1)=YEARWEEK(now(),1)")+")";
|
||||
}else{
|
||||
listSql = listSql.substring(0,listSql.indexOf("limit")) + "where ("+("today".equals(searchDay) ? "to_days(t.create_time) = to_days(now()) " : "YEARWEEK(date_format(t.create_time,'%Y-%m-%d'),1)=YEARWEEK(now(),1)")+") " +(StringUtils.isNotBlank(orderByStr) ? orderByStr : "")+" "+ listSql.substring(listSql.indexOf("limit"));
|
||||
countSql = countSql + " where ("+("today".equals(searchDay) ? "to_days(t.create_time) = to_days(now()) " : "YEARWEEK(date_format(t.create_time,'%Y-%m-%d'),1)=YEARWEEK(now(),1)")+")";
|
||||
}
|
||||
}
|
||||
searchSql.setListSqlString(listSql);
|
||||
searchSql.setClusterSqlString(countSql);
|
||||
}
|
||||
return searchSql;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
String aa = "adasdhask order by as desc limt 5,5";
|
||||
String bb = aa.substring(aa.indexOf("order by"));
|
||||
System.out.println(bb);
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/it/rattan/monisuo/common/PageResult.java
Normal file
40
src/main/java/com/it/rattan/monisuo/common/PageResult.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.it.rattan.monisuo.common;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 数据列表 */
|
||||
private List<T> list;
|
||||
/** 总数 */
|
||||
private long total;
|
||||
/** 当前页 */
|
||||
private long pageNum;
|
||||
/** 每页数量 */
|
||||
private long pageSize;
|
||||
/** 总页数 */
|
||||
private long pages;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public PageResult(List<T> list, long total, long pageNum, long pageSize) {
|
||||
this.list = list;
|
||||
this.total = total;
|
||||
this.pageNum = pageNum;
|
||||
this.pageSize = pageSize;
|
||||
this.pages = (total + pageSize - 1) / pageSize;
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> of(List<T> list, long total, long pageNum, long pageSize) {
|
||||
return new PageResult<>(list, total, pageNum, pageSize);
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/it/rattan/monisuo/common/Result.java
Normal file
64
src/main/java/com/it/rattan/monisuo/common/Result.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.it.rattan.monisuo.common;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结果
|
||||
*/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 成功状态码 */
|
||||
public static final String SUCCESS_CODE = "0000";
|
||||
/** 失败状态码 */
|
||||
public static final String FAIL_CODE = "0001";
|
||||
/** 未授权状态码 */
|
||||
public static final String UNAUTHORIZED_CODE = "0002";
|
||||
|
||||
/** 状态码 */
|
||||
private String code;
|
||||
/** 消息 */
|
||||
private String msg;
|
||||
/** 数据 */
|
||||
private T data;
|
||||
|
||||
public Result() {
|
||||
}
|
||||
|
||||
public Result(String code, String msg, T data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(SUCCESS_CODE, "操作成功", null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(SUCCESS_CODE, "操作成功", data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(String msg, T data) {
|
||||
return new Result<>(SUCCESS_CODE, msg, data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String msg) {
|
||||
return new Result<>(FAIL_CODE, msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String code, String msg) {
|
||||
return new Result<>(code, msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> unauthorized(String msg) {
|
||||
return new Result<>(UNAUTHORIZED_CODE, msg, null);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return SUCCESS_CODE.equals(this.code);
|
||||
}
|
||||
}
|
||||
46
src/main/java/com/it/rattan/monisuo/context/UserContext.java
Normal file
46
src/main/java/com/it/rattan/monisuo/context/UserContext.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.it.rattan.monisuo.context;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户上下文信息
|
||||
*/
|
||||
@Data
|
||||
public class UserContext {
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
/** 用户名 */
|
||||
private String username;
|
||||
/** 类型: user/admin */
|
||||
private String type;
|
||||
|
||||
private static final ThreadLocal<UserContext> CONTEXT = new ThreadLocal<>();
|
||||
|
||||
public static void set(UserContext context) {
|
||||
CONTEXT.set(context);
|
||||
}
|
||||
|
||||
public static UserContext get() {
|
||||
return CONTEXT.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CONTEXT.remove();
|
||||
}
|
||||
|
||||
public static Long getUserId() {
|
||||
UserContext context = get();
|
||||
return context != null ? context.getUserId() : null;
|
||||
}
|
||||
|
||||
public static String getUsername() {
|
||||
UserContext context = get();
|
||||
return context != null ? context.getUsername() : null;
|
||||
}
|
||||
|
||||
public static boolean isAdmin() {
|
||||
UserContext context = get();
|
||||
return context != null && "admin".equals(context.getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package com.it.rattan.monisuo.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.entity.*;
|
||||
import com.it.rattan.monisuo.mapper.AccountFundMapper;
|
||||
import com.it.rattan.monisuo.mapper.OrderFundMapper;
|
||||
import com.it.rattan.monisuo.service.*;
|
||||
import com.it.rattan.monisuo.util.JwtUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 管理后台接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class AdminController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private CoinService coinService;
|
||||
|
||||
@Autowired
|
||||
private FundService fundService;
|
||||
|
||||
@Autowired
|
||||
private AssetService assetService;
|
||||
|
||||
@Autowired
|
||||
private AccountFundMapper accountFundMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderFundMapper orderFundMapper;
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
|
||||
String username = params.get("username");
|
||||
String password = params.get("password");
|
||||
|
||||
if (username == null || password == null) {
|
||||
return Result.fail("用户名和密码不能为空");
|
||||
}
|
||||
|
||||
// 简单验证,实际应从数据库查询
|
||||
// 这里预置账号: admin/admin123, superadmin/admin123
|
||||
String validPassword = "$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9vz.a"; // admin123
|
||||
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder encoder =
|
||||
new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder();
|
||||
|
||||
// 临时处理:预置账号
|
||||
if (("admin".equals(username) || "superadmin".equals(username)) &&
|
||||
encoder.matches(password, validPassword)) {
|
||||
String token = JwtUtil.createToken(1L, username, "admin");
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
Map<String, Object> adminInfo = new HashMap<>();
|
||||
adminInfo.put("id", 1L);
|
||||
adminInfo.put("username", username);
|
||||
adminInfo.put("nickname", "超级管理员");
|
||||
adminInfo.put("role", 1);
|
||||
result.put("adminInfo", adminInfo);
|
||||
return Result.success("登录成功", result);
|
||||
}
|
||||
|
||||
return Result.fail("用户名或密码错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
*/
|
||||
@GetMapping("/user/list")
|
||||
public Result<Map<String, Object>> getUserList(
|
||||
@RequestParam(required = false) String username,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
|
||||
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
|
||||
if (username != null && !username.isEmpty()) {
|
||||
wrapper.like(User::getUsername, username);
|
||||
}
|
||||
if (status != null) {
|
||||
wrapper.eq(User::getStatus, status);
|
||||
}
|
||||
wrapper.orderByDesc(User::getCreateTime);
|
||||
|
||||
Page<User> page = new Page<>(pageNum, pageSize);
|
||||
IPage<User> result = userService.page(page, wrapper);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("list", result.getRecords());
|
||||
data.put("total", result.getTotal());
|
||||
data.put("pageNum", result.getCurrent());
|
||||
data.put("pageSize", result.getSize());
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*/
|
||||
@GetMapping("/user/detail")
|
||||
public Result<Map<String, Object>> getUserDetail(@RequestParam Long userId) {
|
||||
User user = userService.getById(userId);
|
||||
if (user == null) {
|
||||
return Result.fail("用户不存在");
|
||||
}
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("user", user);
|
||||
|
||||
// 资产信息
|
||||
AccountFund fund = assetService.getOrCreateFundAccount(userId);
|
||||
data.put("fund", fund);
|
||||
|
||||
// 交易账户
|
||||
List<Map<String, Object>> trade = assetService.getTradeAccount(userId);
|
||||
data.put("trade", trade);
|
||||
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用/启用用户
|
||||
*/
|
||||
@PostMapping("/user/status")
|
||||
public Result<Void> updateUserStatus(@RequestBody Map<String, Object> params) {
|
||||
Long userId = Long.valueOf(params.get("userId").toString());
|
||||
Integer status = (Integer) params.get("status");
|
||||
|
||||
User user = userService.getById(userId);
|
||||
if (user == null) {
|
||||
return Result.fail("用户不存在");
|
||||
}
|
||||
|
||||
user.setStatus(status);
|
||||
user.setUpdateTime(LocalDateTime.now());
|
||||
userService.updateById(user);
|
||||
|
||||
return Result.success(status == 1 ? "已启用" : "已禁用", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 币种列表
|
||||
*/
|
||||
@GetMapping("/coin/list")
|
||||
public Result<Map<String, Object>> getCoinList() {
|
||||
List<Coin> coins = coinService.list();
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("list", coins);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/编辑币种
|
||||
*/
|
||||
@PostMapping("/coin/save")
|
||||
public Result<Void> saveCoin(@RequestBody Coin coin) {
|
||||
if (coin.getCode() == null || coin.getCode().isEmpty()) {
|
||||
return Result.fail("币种代码不能为空");
|
||||
}
|
||||
if (coin.getName() == null || coin.getName().isEmpty()) {
|
||||
return Result.fail("币种名称不能为空");
|
||||
}
|
||||
|
||||
coin.setCode(coin.getCode().toUpperCase());
|
||||
|
||||
if (coin.getId() == null) {
|
||||
// 新增
|
||||
coin.setCreateTime(LocalDateTime.now());
|
||||
coinService.save(coin);
|
||||
} else {
|
||||
// 编辑
|
||||
coin.setUpdateTime(LocalDateTime.now());
|
||||
coinService.updateById(coin);
|
||||
}
|
||||
|
||||
return Result.success("保存成功", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整币种价格
|
||||
*/
|
||||
@PostMapping("/coin/price")
|
||||
public Result<Void> updateCoinPrice(@RequestBody Map<String, Object> params) {
|
||||
String code = (String) params.get("code");
|
||||
BigDecimal price = new BigDecimal(params.get("price").toString());
|
||||
|
||||
Coin coin = coinService.getCoinByCode(code);
|
||||
if (coin == null) {
|
||||
return Result.fail("币种不存在");
|
||||
}
|
||||
|
||||
if (coin.getPriceType() == 1) {
|
||||
return Result.fail("实时币种价格不可手动修改");
|
||||
}
|
||||
|
||||
coin.setPrice(price);
|
||||
coin.setUpdateTime(LocalDateTime.now());
|
||||
coinService.updateById(coin);
|
||||
|
||||
return Result.success("价格已更新", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 币种上架/下架
|
||||
*/
|
||||
@PostMapping("/coin/status")
|
||||
public Result<Void> updateCoinStatus(@RequestBody Map<String, Object> params) {
|
||||
Long coinId = Long.valueOf(params.get("coinId").toString());
|
||||
Integer status = (Integer) params.get("status");
|
||||
|
||||
Coin coin = coinService.getById(coinId);
|
||||
if (coin == null) {
|
||||
return Result.fail("币种不存在");
|
||||
}
|
||||
|
||||
coin.setStatus(status);
|
||||
coin.setUpdateTime(LocalDateTime.now());
|
||||
coinService.updateById(coin);
|
||||
|
||||
return Result.success(status == 1 ? "已上架" : "已下架", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 价格计算器
|
||||
*/
|
||||
@PostMapping("/coin/calculator")
|
||||
public Result<Map<String, Object>> calculatePrice(@RequestBody Map<String, Object> params) {
|
||||
BigDecimal currentPrice = new BigDecimal(params.get("currentPrice").toString());
|
||||
BigDecimal holdingAmount = new BigDecimal(params.getOrDefault("holdingAmount", "1000").toString());
|
||||
BigDecimal targetProfit = new BigDecimal(params.get("targetProfit").toString());
|
||||
|
||||
// 计算目标价格
|
||||
// 持仓数量 = 持仓金额 / 当前价格
|
||||
BigDecimal quantity = holdingAmount.divide(currentPrice, 8, BigDecimal.ROUND_DOWN);
|
||||
// 单币盈亏 = 目标盈亏 / 持仓数量
|
||||
BigDecimal profitPerCoin = targetProfit.divide(quantity, 8, BigDecimal.ROUND_DOWN);
|
||||
// 目标价格 = 当前价格 + 单币盈亏
|
||||
BigDecimal targetPrice = currentPrice.add(profitPerCoin);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("targetPrice", targetPrice);
|
||||
result.put("quantity", quantity);
|
||||
result.put("profitPerCoin", profitPerCoin);
|
||||
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 待审批订单
|
||||
*/
|
||||
@GetMapping("/order/pending")
|
||||
public Result<Map<String, Object>> getPendingOrders(
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
|
||||
IPage<OrderFund> page = fundService.getPendingOrders(pageNum, pageSize);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("list", page.getRecords());
|
||||
data.put("total", page.getTotal());
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有充提订单
|
||||
*/
|
||||
@GetMapping("/order/list")
|
||||
public Result<Map<String, Object>> getAllOrders(
|
||||
@RequestParam(required = false) Integer type,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
|
||||
IPage<OrderFund> page = fundService.getAllOrders(type, status, pageNum, pageSize);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("list", page.getRecords());
|
||||
data.put("total", page.getTotal());
|
||||
data.put("pageNum", page.getCurrent());
|
||||
data.put("pageSize", page.getSize());
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批订单
|
||||
*/
|
||||
@PostMapping("/order/approve")
|
||||
public Result<Void> approveOrder(@RequestBody Map<String, Object> params) {
|
||||
String orderNo = (String) params.get("orderNo");
|
||||
Integer status = (Integer) params.get("status");
|
||||
String rejectReason = (String) params.get("rejectReason");
|
||||
String adminRemark = (String) params.get("adminRemark");
|
||||
|
||||
if (orderNo == null || status == null) {
|
||||
return Result.fail("参数错误");
|
||||
}
|
||||
|
||||
if (status != 2 && status != 3) {
|
||||
return Result.fail("状态参数错误");
|
||||
}
|
||||
|
||||
try {
|
||||
fundService.approve(1L, "管理员", orderNo, status, rejectReason, adminRemark);
|
||||
return Result.success(status == 2 ? "审批通过" : "已驳回", null);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资金总览
|
||||
*/
|
||||
@GetMapping("/finance/overview")
|
||||
public Result<Map<String, Object>> getFinanceOverview() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
|
||||
// 累计充值
|
||||
BigDecimal totalDeposit = orderFundMapper.sumCompletedDeposit();
|
||||
data.put("totalDeposit", totalDeposit);
|
||||
|
||||
// 累计提现
|
||||
BigDecimal totalWithdraw = orderFundMapper.sumCompletedWithdraw();
|
||||
data.put("totalWithdraw", totalWithdraw);
|
||||
|
||||
// 在管资金
|
||||
BigDecimal fundBalance = accountFundMapper.sumAllBalance();
|
||||
data.put("fundBalance", fundBalance);
|
||||
|
||||
// 交易账户总值
|
||||
BigDecimal tradeValue = accountFundMapper.sumAllTradeValue();
|
||||
data.put("tradeValue", tradeValue != null ? tradeValue : BigDecimal.ZERO);
|
||||
|
||||
// 待审批数量
|
||||
int pendingCount = orderFundMapper.countPending();
|
||||
data.put("pendingCount", pendingCount);
|
||||
|
||||
// 用户总数
|
||||
long userCount = userService.count();
|
||||
data.put("userCount", userCount);
|
||||
|
||||
return Result.success(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.it.rattan.monisuo.controller;
|
||||
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.context.UserContext;
|
||||
import com.it.rattan.monisuo.service.AssetService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 资产接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/asset")
|
||||
public class AssetController {
|
||||
|
||||
@Autowired
|
||||
private AssetService assetService;
|
||||
|
||||
/**
|
||||
* 资产总览
|
||||
*/
|
||||
@GetMapping("/overview")
|
||||
public Result<Map<String, Object>> getOverview() {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = assetService.getOverview(userId);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资金账户
|
||||
*/
|
||||
@GetMapping("/fund")
|
||||
public Result<Map<String, Object>> getFundAccount() {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("fund", assetService.getOrCreateFundAccount(userId));
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 交易账户
|
||||
*/
|
||||
@GetMapping("/trade")
|
||||
public Result<Map<String, Object>> getTradeAccount() {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
List<Map<String, Object>> positions = assetService.getTradeAccount(userId);
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("positions", positions);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资金划转
|
||||
*/
|
||||
@PostMapping("/transfer")
|
||||
public Result<Void> transfer(@RequestBody Map<String, Object> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
Integer direction = (Integer) params.get("direction");
|
||||
BigDecimal amount = new BigDecimal(params.get("amount").toString());
|
||||
|
||||
if (direction == null || (direction != 1 && direction != 2)) {
|
||||
return Result.fail("请选择划转方向");
|
||||
}
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("划转金额必须大于0");
|
||||
}
|
||||
|
||||
try {
|
||||
assetService.transfer(userId, direction, amount);
|
||||
return Result.success("划转成功", null);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资金流水
|
||||
*/
|
||||
@GetMapping("/flow")
|
||||
public Result<Map<String, Object>> getFlows(
|
||||
@RequestParam(required = false) Integer flowType,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
List<?> flows = assetService.getFlows(userId, flowType, pageNum, pageSize);
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("list", flows);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.it.rattan.monisuo.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.context.UserContext;
|
||||
import com.it.rattan.monisuo.entity.OrderFund;
|
||||
import com.it.rattan.monisuo.service.FundService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 充提接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/fund")
|
||||
public class FundController {
|
||||
|
||||
@Autowired
|
||||
private FundService fundService;
|
||||
|
||||
/**
|
||||
* 申请充值
|
||||
*/
|
||||
@PostMapping("/deposit")
|
||||
public Result<Map<String, Object>> deposit(@RequestBody Map<String, Object> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
BigDecimal amount = new BigDecimal(params.get("amount").toString());
|
||||
String remark = (String) params.get("remark");
|
||||
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("充值金额必须大于0");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = fundService.deposit(userId, amount, remark);
|
||||
return Result.success("申请成功,等待审批", result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请提现
|
||||
*/
|
||||
@PostMapping("/withdraw")
|
||||
public Result<Map<String, Object>> withdraw(@RequestBody Map<String, Object> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
BigDecimal amount = new BigDecimal(params.get("amount").toString());
|
||||
String remark = (String) params.get("remark");
|
||||
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("提现金额必须大于0");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = fundService.withdraw(userId, amount, remark);
|
||||
return Result.success("申请成功,等待审批", result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
@PostMapping("/cancel")
|
||||
public Result<Void> cancel(@RequestBody Map<String, String> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
String orderNo = params.get("orderNo");
|
||||
if (orderNo == null || orderNo.isEmpty()) {
|
||||
return Result.fail("订单号不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
fundService.cancel(userId, orderNo);
|
||||
return Result.success("取消成功", null);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 充提记录
|
||||
*/
|
||||
@GetMapping("/orders")
|
||||
public Result<Map<String, Object>> getOrders(
|
||||
@RequestParam(required = false) Integer type,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
IPage<OrderFund> page = fundService.getOrders(userId, type, pageNum, pageSize);
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("list", page.getRecords());
|
||||
result.put("total", page.getTotal());
|
||||
result.put("pageNum", page.getCurrent());
|
||||
result.put("pageSize", page.getSize());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.it.rattan.monisuo.controller;
|
||||
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.entity.Coin;
|
||||
import com.it.rattan.monisuo.service.CoinService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 行情接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/market")
|
||||
public class MarketController {
|
||||
|
||||
@Autowired
|
||||
private CoinService coinService;
|
||||
|
||||
/**
|
||||
* 币种列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<Map<String, Object>> getCoinList() {
|
||||
try {
|
||||
List<Coin> coins = coinService.getActiveCoins();
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("list", coins);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 币种详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
public Result<Coin> getCoinDetail(@RequestParam String code) {
|
||||
try {
|
||||
Coin coin = coinService.getCoinByCode(code);
|
||||
if (coin == null) {
|
||||
return Result.fail("币种不存在");
|
||||
}
|
||||
return Result.success(coin);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索币种
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public Result<Map<String, Object>> searchCoins(@RequestParam String keyword) {
|
||||
try {
|
||||
List<Coin> coins = coinService.getActiveCoins();
|
||||
List<Coin> filtered = new java.util.ArrayList<>();
|
||||
for (Coin coin : coins) {
|
||||
if (coin.getCode().toLowerCase().contains(keyword.toLowerCase()) ||
|
||||
coin.getName().toLowerCase().contains(keyword.toLowerCase())) {
|
||||
filtered.add(coin);
|
||||
}
|
||||
}
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("list", filtered);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.it.rattan.monisuo.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.context.UserContext;
|
||||
import com.it.rattan.monisuo.entity.OrderTrade;
|
||||
import com.it.rattan.monisuo.service.TradeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 交易接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/trade")
|
||||
public class TradeController {
|
||||
|
||||
@Autowired
|
||||
private TradeService tradeService;
|
||||
|
||||
/**
|
||||
* 买入
|
||||
*/
|
||||
@PostMapping("/buy")
|
||||
public Result<Map<String, Object>> buy(@RequestBody Map<String, Object> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
String coinCode = (String) params.get("coinCode");
|
||||
BigDecimal price = new BigDecimal(params.get("price").toString());
|
||||
BigDecimal quantity = new BigDecimal(params.get("quantity").toString());
|
||||
|
||||
if (coinCode == null || coinCode.isEmpty()) {
|
||||
return Result.fail("请选择币种");
|
||||
}
|
||||
if (price.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("价格必须大于0");
|
||||
}
|
||||
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("数量必须大于0");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = tradeService.buy(userId, coinCode.toUpperCase(), price, quantity);
|
||||
return Result.success("买入成功", result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卖出
|
||||
*/
|
||||
@PostMapping("/sell")
|
||||
public Result<Map<String, Object>> sell(@RequestBody Map<String, Object> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
String coinCode = (String) params.get("coinCode");
|
||||
BigDecimal price = new BigDecimal(params.get("price").toString());
|
||||
BigDecimal quantity = new BigDecimal(params.get("quantity").toString());
|
||||
|
||||
if (coinCode == null || coinCode.isEmpty()) {
|
||||
return Result.fail("请选择币种");
|
||||
}
|
||||
if (price.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("价格必须大于0");
|
||||
}
|
||||
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.fail("数量必须大于0");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = tradeService.sell(userId, coinCode.toUpperCase(), price, quantity);
|
||||
return Result.success("卖出成功", result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 交易记录
|
||||
*/
|
||||
@GetMapping("/orders")
|
||||
public Result<Map<String, Object>> getOrders(
|
||||
@RequestParam(required = false) String coinCode,
|
||||
@RequestParam(required = false) Integer direction,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
IPage<OrderTrade> page = tradeService.getOrders(userId, coinCode, direction, pageNum, pageSize);
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("list", page.getRecords());
|
||||
result.put("total", page.getTotal());
|
||||
result.put("pageNum", page.getCurrent());
|
||||
result.put("pageSize", page.getSize());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单详情
|
||||
*/
|
||||
@GetMapping("/order/detail")
|
||||
public Result<OrderTrade> getOrderDetail(@RequestParam String orderNo) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
OrderTrade order = tradeService.getOrderDetail(userId, orderNo);
|
||||
if (order == null) {
|
||||
return Result.fail("订单不存在");
|
||||
}
|
||||
return Result.success(order);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.it.rattan.monisuo.controller;
|
||||
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.context.UserContext;
|
||||
import com.it.rattan.monisuo.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public Result<Map<String, Object>> register(@RequestBody Map<String, String> params) {
|
||||
String username = params.get("username");
|
||||
String password = params.get("password");
|
||||
|
||||
if (username == null || username.trim().isEmpty()) {
|
||||
return Result.fail("用户名不能为空");
|
||||
}
|
||||
if (password == null || password.length() < 6) {
|
||||
return Result.fail("密码长度至少6位");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = userService.register(username.trim(), password);
|
||||
return Result.success("注册成功", result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
|
||||
String username = params.get("username");
|
||||
String password = params.get("password");
|
||||
|
||||
if (username == null || username.trim().isEmpty()) {
|
||||
return Result.fail("用户名不能为空");
|
||||
}
|
||||
if (password == null || password.isEmpty()) {
|
||||
return Result.fail("密码不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = userService.login(username.trim(), password);
|
||||
return Result.success("登录成功", result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public Result<Map<String, Object>> getUserInfo() {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> result = userService.getUserInfo(userId);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传KYC资料
|
||||
*/
|
||||
@PostMapping("/kyc")
|
||||
public Result<Void> uploadKyc(@RequestBody Map<String, String> params) {
|
||||
Long userId = UserContext.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.unauthorized("请先登录");
|
||||
}
|
||||
|
||||
String idCardFront = params.get("idCardFront");
|
||||
String idCardBack = params.get("idCardBack");
|
||||
|
||||
if (idCardFront == null || idCardFront.isEmpty()) {
|
||||
return Result.fail("请上传身份证正面照");
|
||||
}
|
||||
if (idCardBack == null || idCardBack.isEmpty()) {
|
||||
return Result.fail("请上传身份证反面照");
|
||||
}
|
||||
|
||||
try {
|
||||
userService.uploadKyc(userId, idCardFront, idCardBack);
|
||||
return Result.success("上传成功", null);
|
||||
} catch (Exception e) {
|
||||
return Result.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout() {
|
||||
// 客户端清除Token即可
|
||||
return Result.success("退出成功", null);
|
||||
}
|
||||
}
|
||||
51
src/main/java/com/it/rattan/monisuo/entity/AccountFlow.java
Normal file
51
src/main/java/com/it/rattan/monisuo/entity/AccountFlow.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 资金流水实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("account_flow")
|
||||
public class AccountFlow implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 流水号 */
|
||||
private String flowNo;
|
||||
|
||||
/** 流水类型: 1-充值 2-提现 3-划转转入 4-划转转出 5-买入 6-卖出 */
|
||||
private Integer flowType;
|
||||
|
||||
/** 变动金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 变动前余额 */
|
||||
private BigDecimal balanceBefore;
|
||||
|
||||
/** 变动后余额 */
|
||||
private BigDecimal balanceAfter;
|
||||
|
||||
/** 相关币种 */
|
||||
private String coinCode;
|
||||
|
||||
/** 关联订单号 */
|
||||
private String relatedOrderNo;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
43
src/main/java/com/it/rattan/monisuo/entity/AccountFund.java
Normal file
43
src/main/java/com/it/rattan/monisuo/entity/AccountFund.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 资金账户实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("account_fund")
|
||||
public class AccountFund implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** USDT余额 */
|
||||
private BigDecimal balance;
|
||||
|
||||
/** 冻结金额 */
|
||||
private BigDecimal frozen;
|
||||
|
||||
/** 累计充值 */
|
||||
private BigDecimal totalDeposit;
|
||||
|
||||
/** 累计提现 */
|
||||
private BigDecimal totalWithdraw;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
49
src/main/java/com/it/rattan/monisuo/entity/AccountTrade.java
Normal file
49
src/main/java/com/it/rattan/monisuo/entity/AccountTrade.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 交易账户实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("account_trade")
|
||||
public class AccountTrade implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 币种代码 */
|
||||
private String coinCode;
|
||||
|
||||
/** 持仓数量 */
|
||||
private BigDecimal quantity;
|
||||
|
||||
/** 冻结数量 */
|
||||
private BigDecimal frozen;
|
||||
|
||||
/** 平均成本价 */
|
||||
private BigDecimal avgPrice;
|
||||
|
||||
/** 累计买入数量 */
|
||||
private BigDecimal totalBuy;
|
||||
|
||||
/** 累计卖出数量 */
|
||||
private BigDecimal totalSell;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
61
src/main/java/com/it/rattan/monisuo/entity/Admin.java
Normal file
61
src/main/java/com/it/rattan/monisuo/entity/Admin.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 管理员实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_admin")
|
||||
public class Admin implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 账号 */
|
||||
private String username;
|
||||
|
||||
/** 密码(BCrypt加密) */
|
||||
private String password;
|
||||
|
||||
/** 昵称 */
|
||||
private String nickname;
|
||||
|
||||
/** 头像URL */
|
||||
private String avatar;
|
||||
|
||||
/** 角色: 1-超级管理员 2-普通管理员 */
|
||||
private Integer role;
|
||||
|
||||
/** 权限列表(JSON格式) */
|
||||
private String permissions;
|
||||
|
||||
/** 状态: 0-禁用 1-正常 */
|
||||
private Integer status;
|
||||
|
||||
/** 是否系统预置: 0-否 1-是 */
|
||||
private Integer isSystem;
|
||||
|
||||
/** 最后登录时间 */
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
/** 最后登录IP */
|
||||
private String lastLoginIp;
|
||||
|
||||
/** 当前Token */
|
||||
@TableField(select = false)
|
||||
private String token;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
91
src/main/java/com/it/rattan/monisuo/entity/Coin.java
Normal file
91
src/main/java/com/it/rattan/monisuo/entity/Coin.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 币种实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("coin")
|
||||
public class Coin implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 币种代码(如BTC) */
|
||||
private String code;
|
||||
|
||||
/** 币种名称(如Bitcoin) */
|
||||
private String name;
|
||||
|
||||
/** 币种图标URL */
|
||||
private String icon;
|
||||
|
||||
/** 当前价格(USDT) */
|
||||
private BigDecimal price;
|
||||
|
||||
/** 美元价格 */
|
||||
private BigDecimal priceUsd;
|
||||
|
||||
/** 人民币价格 */
|
||||
private BigDecimal priceCny;
|
||||
|
||||
/** 价格类型: 1-实时 2-管理 */
|
||||
private Integer priceType;
|
||||
|
||||
/** 24小时涨跌幅(%) */
|
||||
private BigDecimal change24h;
|
||||
|
||||
/** 24小时最高价 */
|
||||
private BigDecimal high24h;
|
||||
|
||||
/** 24小时最低价 */
|
||||
private BigDecimal low24h;
|
||||
|
||||
/** 24小时交易量 */
|
||||
private BigDecimal volume24h;
|
||||
|
||||
/** 市值 */
|
||||
private BigDecimal marketCap;
|
||||
|
||||
/** 总发行量 */
|
||||
private BigDecimal totalSupply;
|
||||
|
||||
/** 流通量 */
|
||||
private BigDecimal circulatingSupply;
|
||||
|
||||
/** 币种简介 */
|
||||
private String description;
|
||||
|
||||
/** 官网链接 */
|
||||
private String website;
|
||||
|
||||
/** 价格小数位 */
|
||||
private Integer priceScale;
|
||||
|
||||
/** 数量小数位 */
|
||||
private Integer quantityScale;
|
||||
|
||||
/** 最小交易数量 */
|
||||
private BigDecimal minQuantity;
|
||||
|
||||
/** 状态: 0-下架 1-上架 */
|
||||
private Integer status;
|
||||
|
||||
/** 排序权重(越大越靠前) */
|
||||
private Integer sort;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
64
src/main/java/com/it/rattan/monisuo/entity/OrderFund.java
Normal file
64
src/main/java/com/it/rattan/monisuo/entity/OrderFund.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 充提订单实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_fund")
|
||||
public class OrderFund implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 订单号 */
|
||||
private String orderNo;
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 用户账号(冗余) */
|
||||
private String username;
|
||||
|
||||
/** 类型: 1-充值 2-提现 */
|
||||
private Integer type;
|
||||
|
||||
/** 金额(USDT) */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 状态: 1-待审批 2-已完成 3-已驳回 4-已取消 */
|
||||
private Integer status;
|
||||
|
||||
/** 审批管理员ID */
|
||||
private Long approveAdminId;
|
||||
|
||||
/** 审批管理员名称 */
|
||||
private String approveAdminName;
|
||||
|
||||
/** 审批时间 */
|
||||
private LocalDateTime approveTime;
|
||||
|
||||
/** 驳回原因 */
|
||||
private String rejectReason;
|
||||
|
||||
/** 用户备注 */
|
||||
private String remark;
|
||||
|
||||
/** 管理员备注 */
|
||||
private String adminRemark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
61
src/main/java/com/it/rattan/monisuo/entity/OrderTrade.java
Normal file
61
src/main/java/com/it/rattan/monisuo/entity/OrderTrade.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 交易订单实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_trade")
|
||||
public class OrderTrade implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 订单号 */
|
||||
private String orderNo;
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 交易币种代码 */
|
||||
private String coinCode;
|
||||
|
||||
/** 交易方向: 1-买入 2-卖出 */
|
||||
private Integer direction;
|
||||
|
||||
/** 订单类型: 1-市价 2-限价 */
|
||||
private Integer orderType;
|
||||
|
||||
/** 成交价格 */
|
||||
private BigDecimal price;
|
||||
|
||||
/** 成交数量 */
|
||||
private BigDecimal quantity;
|
||||
|
||||
/** 成交金额(USDT) */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 手续费 */
|
||||
private BigDecimal fee;
|
||||
|
||||
/** 状态: 1-成功 2-失败 3-已取消 */
|
||||
private Integer status;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
68
src/main/java/com/it/rattan/monisuo/entity/User.java
Normal file
68
src/main/java/com/it/rattan/monisuo/entity/User.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package com.it.rattan.monisuo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_user")
|
||||
public class User implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 账号 */
|
||||
private String username;
|
||||
|
||||
/** 密码(BCrypt加密) */
|
||||
private String password;
|
||||
|
||||
/** 昵称 */
|
||||
private String nickname;
|
||||
|
||||
/** 头像URL */
|
||||
private String avatar;
|
||||
|
||||
/** 手机号 */
|
||||
private String phone;
|
||||
|
||||
/** 邮箱 */
|
||||
private String email;
|
||||
|
||||
/** KYC状态: 0-未激活 1-已激活 */
|
||||
private Integer kycStatus;
|
||||
|
||||
/** 身份证正面照URL */
|
||||
private String idCardFront;
|
||||
|
||||
/** 身份证反面照URL */
|
||||
private String idCardBack;
|
||||
|
||||
/** 状态: 0-禁用 1-正常 */
|
||||
private Integer status;
|
||||
|
||||
/** 最后登录时间 */
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
/** 最后登录IP */
|
||||
private String lastLoginIp;
|
||||
|
||||
/** 当前Token */
|
||||
@TableField(select = false)
|
||||
private String token;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
86
src/main/java/com/it/rattan/monisuo/filter/TokenFilter.java
Normal file
86
src/main/java/com/it/rattan/monisuo/filter/TokenFilter.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.it.rattan.monisuo.filter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.it.rattan.monisuo.common.Result;
|
||||
import com.it.rattan.monisuo.context.UserContext;
|
||||
import com.it.rattan.monisuo.util.JwtUtil;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Token过滤器
|
||||
*/
|
||||
@Component
|
||||
@Order(1)
|
||||
public class TokenFilter implements Filter {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
/** 不需要验证的路径 */
|
||||
private static final String[] EXCLUDE_PATHS = {
|
||||
"/api/user/register",
|
||||
"/api/user/login",
|
||||
"/admin/login",
|
||||
"/swagger-resources",
|
||||
"/v2/api-docs",
|
||||
"/webjars/",
|
||||
"/swagger-ui.html"
|
||||
};
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
String uri = httpRequest.getRequestURI();
|
||||
|
||||
// 检查是否排除路径
|
||||
for (String excludePath : EXCLUDE_PATHS) {
|
||||
if (uri.contains(excludePath)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Token
|
||||
String token = httpRequest.getHeader("Authorization");
|
||||
if (token != null && token.startsWith("Bearer ")) {
|
||||
token = token.substring(7);
|
||||
}
|
||||
|
||||
if (token == null || token.isEmpty()) {
|
||||
writeUnauthorized(httpResponse, "请先登录");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证Token
|
||||
if (!JwtUtil.isValid(token)) {
|
||||
writeUnauthorized(httpResponse, "Token已过期,请重新登录");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置用户上下文
|
||||
UserContext context = new UserContext();
|
||||
context.setUserId(JwtUtil.getUserId(token));
|
||||
context.setUsername(JwtUtil.getUsername(token));
|
||||
context.setType(JwtUtil.getType(token));
|
||||
UserContext.set(context);
|
||||
|
||||
try {
|
||||
chain.doFilter(request, response);
|
||||
} finally {
|
||||
UserContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeUnauthorized(HttpServletResponse response, String message) throws IOException {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(Result.unauthorized(message)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.AccountFlow;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 资金流水Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AccountFlowMapper extends BaseMapper<AccountFlow> {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.AccountFund;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 资金账户Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AccountFundMapper extends BaseMapper<AccountFund> {
|
||||
|
||||
@Select("SELECT IFNULL(SUM(balance), 0) FROM account_fund")
|
||||
BigDecimal sumAllBalance();
|
||||
|
||||
@Select("SELECT IFNULL(SUM(total_deposit), 0) FROM account_fund")
|
||||
BigDecimal sumTotalDeposit();
|
||||
|
||||
@Select("SELECT IFNULL(SUM(total_withdraw), 0) FROM account_fund")
|
||||
BigDecimal sumTotalWithdraw();
|
||||
|
||||
@Select("SELECT IFNULL(SUM(quantity * avg_price), 0) FROM account_trade")
|
||||
BigDecimal sumAllTradeValue();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.AccountTrade;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 交易账户Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AccountTradeMapper extends BaseMapper<AccountTrade> {
|
||||
|
||||
@Select("SELECT IFNULL(SUM(at.quantity * c.price), 0) FROM account_trade at " +
|
||||
"LEFT JOIN coin c ON at.coin_code = c.code " +
|
||||
"WHERE at.quantity > 0")
|
||||
BigDecimal sumAllTradeValue();
|
||||
}
|
||||
12
src/main/java/com/it/rattan/monisuo/mapper/AdminMapper.java
Normal file
12
src/main/java/com/it/rattan/monisuo/mapper/AdminMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.Admin;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 管理员Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminMapper extends BaseMapper<Admin> {
|
||||
}
|
||||
12
src/main/java/com/it/rattan/monisuo/mapper/CoinMapper.java
Normal file
12
src/main/java/com/it/rattan/monisuo/mapper/CoinMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.Coin;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 币种Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface CoinMapper extends BaseMapper<Coin> {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.OrderFund;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 充提订单Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderFundMapper extends BaseMapper<OrderFund> {
|
||||
|
||||
@Select("SELECT IFNULL(SUM(amount), 0) FROM order_fund WHERE type = 1 AND status = 2")
|
||||
BigDecimal sumCompletedDeposit();
|
||||
|
||||
@Select("SELECT IFNULL(SUM(amount), 0) FROM order_fund WHERE type = 2 AND status = 2")
|
||||
BigDecimal sumCompletedWithdraw();
|
||||
|
||||
@Select("SELECT COUNT(*) FROM order_fund WHERE status = 1")
|
||||
int countPending();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.OrderTrade;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 交易订单Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderTradeMapper extends BaseMapper<OrderTrade> {
|
||||
}
|
||||
12
src/main/java/com/it/rattan/monisuo/mapper/UserMapper.java
Normal file
12
src/main/java/com/it/rattan/monisuo/mapper/UserMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.it.rattan.monisuo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.it.rattan.monisuo.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
}
|
||||
256
src/main/java/com/it/rattan/monisuo/service/AssetService.java
Normal file
256
src/main/java/com/it/rattan/monisuo/service/AssetService.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package com.it.rattan.monisuo.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.it.rattan.monisuo.entity.AccountFlow;
|
||||
import com.it.rattan.monisuo.entity.AccountFund;
|
||||
import com.it.rattan.monisuo.entity.AccountTrade;
|
||||
import com.it.rattan.monisuo.entity.Coin;
|
||||
import com.it.rattan.monisuo.mapper.AccountFlowMapper;
|
||||
import com.it.rattan.monisuo.mapper.AccountFundMapper;
|
||||
import com.it.rattan.monisuo.mapper.AccountTradeMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.it.rattan.monisuo.util.OrderNoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 资产服务
|
||||
*/
|
||||
@Service
|
||||
public class AssetService {
|
||||
|
||||
@Autowired
|
||||
private AccountFundMapper accountFundMapper;
|
||||
|
||||
@Autowired
|
||||
private AccountTradeMapper accountTradeMapper;
|
||||
|
||||
@Autowired
|
||||
private AccountFlowMapper accountFlowMapper;
|
||||
|
||||
@Autowired
|
||||
private CoinService coinService;
|
||||
|
||||
/**
|
||||
* 获取资产总览
|
||||
*/
|
||||
public Map<String, Object> getOverview(Long userId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 资金账户
|
||||
AccountFund fund = getOrCreateFundAccount(userId);
|
||||
result.put("fundBalance", fund.getBalance());
|
||||
result.put("fundFrozen", fund.getFrozen());
|
||||
|
||||
// 交易账户
|
||||
BigDecimal tradeValue = BigDecimal.ZERO;
|
||||
List<Map<String, Object>> positions = new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<AccountTrade> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(AccountTrade::getUserId, userId)
|
||||
.gt(AccountTrade::getQuantity, BigDecimal.ZERO);
|
||||
List<AccountTrade> trades = accountTradeMapper.selectList(wrapper);
|
||||
|
||||
for (AccountTrade trade : trades) {
|
||||
Coin coin = coinService.getCoinByCode(trade.getCoinCode());
|
||||
if (coin != null) {
|
||||
BigDecimal value = trade.getQuantity().multiply(coin.getPrice())
|
||||
.setScale(8, RoundingMode.DOWN);
|
||||
tradeValue = tradeValue.add(value);
|
||||
|
||||
Map<String, Object> position = new HashMap<>();
|
||||
position.put("coinCode", trade.getCoinCode());
|
||||
position.put("coinName", coin.getName());
|
||||
position.put("quantity", trade.getQuantity());
|
||||
position.put("price", coin.getPrice());
|
||||
position.put("value", value);
|
||||
position.put("avgPrice", trade.getAvgPrice());
|
||||
positions.add(position);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("tradeValue", tradeValue);
|
||||
result.put("positions", positions);
|
||||
|
||||
// 总资产
|
||||
BigDecimal totalAssets = fund.getBalance().add(tradeValue);
|
||||
result.put("totalAssets", totalAssets);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资金账户
|
||||
*/
|
||||
public AccountFund getOrCreateFundAccount(Long userId) {
|
||||
LambdaQueryWrapper<AccountFund> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(AccountFund::getUserId, userId);
|
||||
AccountFund fund = accountFundMapper.selectOne(wrapper);
|
||||
|
||||
if (fund == null) {
|
||||
fund = new AccountFund();
|
||||
fund.setUserId(userId);
|
||||
fund.setBalance(BigDecimal.ZERO);
|
||||
fund.setFrozen(BigDecimal.ZERO);
|
||||
fund.setTotalDeposit(BigDecimal.ZERO);
|
||||
fund.setTotalWithdraw(BigDecimal.ZERO);
|
||||
fund.setCreateTime(LocalDateTime.now());
|
||||
accountFundMapper.insert(fund);
|
||||
}
|
||||
|
||||
return fund;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取交易账户
|
||||
*/
|
||||
public List<Map<String, Object>> getTradeAccount(Long userId) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<AccountTrade> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(AccountTrade::getUserId, userId)
|
||||
.gt(AccountTrade::getQuantity, BigDecimal.ZERO);
|
||||
List<AccountTrade> trades = accountTradeMapper.selectList(wrapper);
|
||||
|
||||
for (AccountTrade trade : trades) {
|
||||
Coin coin = coinService.getCoinByCode(trade.getCoinCode());
|
||||
if (coin != null) {
|
||||
BigDecimal value = trade.getQuantity().multiply(coin.getPrice())
|
||||
.setScale(8, RoundingMode.DOWN);
|
||||
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("coinCode", trade.getCoinCode());
|
||||
item.put("coinName", coin.getName());
|
||||
item.put("coinIcon", coin.getIcon());
|
||||
item.put("quantity", trade.getQuantity());
|
||||
item.put("price", coin.getPrice());
|
||||
item.put("value", value);
|
||||
item.put("avgPrice", trade.getAvgPrice());
|
||||
item.put("change24h", coin.getChange24h());
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资金划转
|
||||
*/
|
||||
@Transactional
|
||||
public void transfer(Long userId, Integer direction, BigDecimal amount) {
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("划转金额必须大于0");
|
||||
}
|
||||
|
||||
AccountFund fund = getOrCreateFundAccount(userId);
|
||||
|
||||
// 获取交易账户USDT持仓
|
||||
AccountTrade tradeUsdt = getOrCreateTradeAccount(userId, "USDT");
|
||||
|
||||
if (direction == 1) {
|
||||
// 资金账户 -> 交易账户
|
||||
if (fund.getBalance().compareTo(amount) < 0) {
|
||||
throw new RuntimeException("资金账户余额不足");
|
||||
}
|
||||
fund.setBalance(fund.getBalance().subtract(amount));
|
||||
tradeUsdt.setQuantity(tradeUsdt.getQuantity().add(amount));
|
||||
|
||||
// 记录流水
|
||||
createFlow(userId, 4, amount.negate(), fund.getBalance().add(amount),
|
||||
fund.getBalance(), "USDT", null, "划转至交易账户");
|
||||
|
||||
} else if (direction == 2) {
|
||||
// 交易账户 -> 资金账户
|
||||
if (tradeUsdt.getQuantity().compareTo(amount) < 0) {
|
||||
throw new RuntimeException("交易账户USDT余额不足");
|
||||
}
|
||||
tradeUsdt.setQuantity(tradeUsdt.getQuantity().subtract(amount));
|
||||
fund.setBalance(fund.getBalance().add(amount));
|
||||
|
||||
// 记录流水
|
||||
createFlow(userId, 3, amount, fund.getBalance().subtract(amount),
|
||||
fund.getBalance(), "USDT", null, "划转至资金账户");
|
||||
|
||||
} else {
|
||||
throw new RuntimeException("无效的划转方向");
|
||||
}
|
||||
|
||||
fund.setUpdateTime(LocalDateTime.now());
|
||||
accountFundMapper.updateById(fund);
|
||||
|
||||
tradeUsdt.setUpdateTime(LocalDateTime.now());
|
||||
accountTradeMapper.updateById(tradeUsdt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建交易账户
|
||||
*/
|
||||
public AccountTrade getOrCreateTradeAccount(Long userId, String coinCode) {
|
||||
LambdaQueryWrapper<AccountTrade> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(AccountTrade::getUserId, userId)
|
||||
.eq(AccountTrade::getCoinCode, coinCode.toUpperCase());
|
||||
AccountTrade trade = accountTradeMapper.selectOne(wrapper);
|
||||
|
||||
if (trade == null) {
|
||||
trade = new AccountTrade();
|
||||
trade.setUserId(userId);
|
||||
trade.setCoinCode(coinCode.toUpperCase());
|
||||
trade.setQuantity(BigDecimal.ZERO);
|
||||
trade.setFrozen(BigDecimal.ZERO);
|
||||
trade.setAvgPrice(BigDecimal.ZERO);
|
||||
trade.setTotalBuy(BigDecimal.ZERO);
|
||||
trade.setTotalSell(BigDecimal.ZERO);
|
||||
trade.setCreateTime(LocalDateTime.now());
|
||||
accountTradeMapper.insert(trade);
|
||||
}
|
||||
|
||||
return trade;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建资金流水
|
||||
*/
|
||||
public void createFlow(Long userId, Integer flowType, BigDecimal amount,
|
||||
BigDecimal balanceBefore, BigDecimal balanceAfter,
|
||||
String coinCode, String relatedOrderNo, String remark) {
|
||||
AccountFlow flow = new AccountFlow();
|
||||
flow.setUserId(userId);
|
||||
flow.setFlowNo(OrderNoUtil.flowNo());
|
||||
flow.setFlowType(flowType);
|
||||
flow.setAmount(amount);
|
||||
flow.setBalanceBefore(balanceBefore);
|
||||
flow.setBalanceAfter(balanceAfter);
|
||||
flow.setCoinCode(coinCode);
|
||||
flow.setRelatedOrderNo(relatedOrderNo);
|
||||
flow.setRemark(remark);
|
||||
flow.setCreateTime(LocalDateTime.now());
|
||||
accountFlowMapper.insert(flow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资金流水
|
||||
*/
|
||||
public List<AccountFlow> getFlows(Long userId, Integer flowType, int page, int size) {
|
||||
LambdaQueryWrapper<AccountFlow> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(AccountFlow::getUserId, userId);
|
||||
if (flowType != null && flowType > 0) {
|
||||
wrapper.eq(AccountFlow::getFlowType, flowType);
|
||||
}
|
||||
wrapper.orderByDesc(AccountFlow::getCreateTime);
|
||||
wrapper.last("LIMIT " + (page - 1) * size + ", " + size);
|
||||
return accountFlowMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新资金账户
|
||||
*/
|
||||
public void updateFundAccount(LambdaUpdateWrapper<AccountFund> updateWrapper) {
|
||||
accountFundMapper.update(null, updateWrapper);
|
||||
}
|
||||
}
|
||||
58
src/main/java/com/it/rattan/monisuo/service/CoinService.java
Normal file
58
src/main/java/com/it/rattan/monisuo/service/CoinService.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package com.it.rattan.monisuo.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.it.rattan.monisuo.entity.Coin;
|
||||
import com.it.rattan.monisuo.mapper.CoinMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 币种服务
|
||||
*/
|
||||
@Service
|
||||
public class CoinService extends ServiceImpl<CoinMapper, Coin> {
|
||||
|
||||
/**
|
||||
* 获取所有上架币种
|
||||
*/
|
||||
public List<Coin> getActiveCoins() {
|
||||
LambdaQueryWrapper<Coin> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Coin::getStatus, 1)
|
||||
.orderByDesc(Coin::getSort);
|
||||
return list(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取币种
|
||||
*/
|
||||
public Coin getCoinByCode(String code) {
|
||||
LambdaQueryWrapper<Coin> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Coin::getCode, code.toUpperCase())
|
||||
.eq(Coin::getStatus, 1);
|
||||
return getOne(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新币种价格
|
||||
*/
|
||||
public void updatePrice(String code, BigDecimal price) {
|
||||
Coin coin = getCoinByCode(code);
|
||||
if (coin != null) {
|
||||
coin.setPrice(price);
|
||||
coin.setUpdateTime(java.time.LocalDateTime.now());
|
||||
updateById(coin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实时币种列表
|
||||
*/
|
||||
public List<Coin> getRealTimeCoins() {
|
||||
LambdaQueryWrapper<Coin> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Coin::getPriceType, 1)
|
||||
.eq(Coin::getStatus, 1);
|
||||
return list(wrapper);
|
||||
}
|
||||
}
|
||||
244
src/main/java/com/it/rattan/monisuo/service/FundService.java
Normal file
244
src/main/java/com/it/rattan/monisuo/service/FundService.java
Normal file
@@ -0,0 +1,244 @@
|
||||
package com.it.rattan.monisuo.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.it.rattan.monisuo.entity.AccountFund;
|
||||
import com.it.rattan.monisuo.entity.OrderFund;
|
||||
import com.it.rattan.monisuo.entity.User;
|
||||
import com.it.rattan.monisuo.mapper.OrderFundMapper;
|
||||
import com.it.rattan.monisuo.mapper.UserMapper;
|
||||
import com.it.rattan.monisuo.util.OrderNoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 充提服务
|
||||
*/
|
||||
@Service
|
||||
public class FundService {
|
||||
|
||||
@Autowired
|
||||
private OrderFundMapper orderFundMapper;
|
||||
|
||||
@Autowired
|
||||
private AssetService assetService;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
/**
|
||||
* 申请充值
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> deposit(Long userId, BigDecimal amount, String remark) {
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("充值金额必须大于0");
|
||||
}
|
||||
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
|
||||
OrderFund order = new OrderFund();
|
||||
order.setOrderNo(OrderNoUtil.fundOrderNo());
|
||||
order.setUserId(userId);
|
||||
order.setUsername(user.getUsername());
|
||||
order.setType(1); // 充值
|
||||
order.setAmount(amount);
|
||||
order.setStatus(1); // 待审批
|
||||
order.setRemark(remark);
|
||||
order.setCreateTime(LocalDateTime.now());
|
||||
orderFundMapper.insert(order);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orderNo", order.getOrderNo());
|
||||
result.put("amount", amount);
|
||||
result.put("status", order.getStatus());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请提现
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> withdraw(Long userId, BigDecimal amount, String remark) {
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("提现金额必须大于0");
|
||||
}
|
||||
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
|
||||
// 检查余额
|
||||
AccountFund fund = assetService.getOrCreateFundAccount(userId);
|
||||
if (fund.getBalance().compareTo(amount) < 0) {
|
||||
throw new RuntimeException("资金账户余额不足");
|
||||
}
|
||||
|
||||
OrderFund order = new OrderFund();
|
||||
order.setOrderNo(OrderNoUtil.fundOrderNo());
|
||||
order.setUserId(userId);
|
||||
order.setUsername(user.getUsername());
|
||||
order.setType(2); // 提现
|
||||
order.setAmount(amount);
|
||||
order.setStatus(1); // 待审批
|
||||
order.setRemark(remark);
|
||||
order.setCreateTime(LocalDateTime.now());
|
||||
orderFundMapper.insert(order);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orderNo", order.getOrderNo());
|
||||
result.put("amount", amount);
|
||||
result.put("status", order.getStatus());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
@Transactional
|
||||
public void cancel(Long userId, String orderNo) {
|
||||
LambdaQueryWrapper<OrderFund> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(OrderFund::getUserId, userId)
|
||||
.eq(OrderFund::getOrderNo, orderNo)
|
||||
.eq(OrderFund::getStatus, 1); // 仅待审批可取消
|
||||
|
||||
OrderFund order = orderFundMapper.selectOne(wrapper);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在或状态不可取消");
|
||||
}
|
||||
|
||||
order.setStatus(4); // 已取消
|
||||
order.setUpdateTime(LocalDateTime.now());
|
||||
orderFundMapper.updateById(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取充提记录
|
||||
*/
|
||||
public IPage<OrderFund> getOrders(Long userId, Integer type, int pageNum, int pageSize) {
|
||||
LambdaQueryWrapper<OrderFund> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(OrderFund::getUserId, userId);
|
||||
if (type != null && type > 0) {
|
||||
wrapper.eq(OrderFund::getType, type);
|
||||
}
|
||||
wrapper.orderByDesc(OrderFund::getCreateTime);
|
||||
|
||||
Page<OrderFund> page = new Page<>(pageNum, pageSize);
|
||||
return orderFundMapper.selectPage(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待审批订单数量
|
||||
*/
|
||||
public int getPendingCount() {
|
||||
return orderFundMapper.countPending();
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员审批
|
||||
*/
|
||||
@Transactional
|
||||
public void approve(Long adminId, String adminName, String orderNo, Integer status,
|
||||
String rejectReason, String adminRemark) {
|
||||
OrderFund order = orderFundMapper.selectOne(
|
||||
new LambdaQueryWrapper<OrderFund>().eq(OrderFund::getOrderNo, orderNo));
|
||||
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
|
||||
if (order.getStatus() != 1) {
|
||||
throw new RuntimeException("订单已处理");
|
||||
}
|
||||
|
||||
if (status == 2) {
|
||||
// 审批通过
|
||||
AccountFund fund = assetService.getOrCreateFundAccount(order.getUserId());
|
||||
|
||||
if (order.getType() == 1) {
|
||||
// 充值:增加余额
|
||||
fund.setBalance(fund.getBalance().add(order.getAmount()));
|
||||
fund.setTotalDeposit(fund.getTotalDeposit().add(order.getAmount()));
|
||||
} else {
|
||||
// 提现:扣减余额
|
||||
if (fund.getBalance().compareTo(order.getAmount()) < 0) {
|
||||
throw new RuntimeException("用户余额不足");
|
||||
}
|
||||
fund.setBalance(fund.getBalance().subtract(order.getAmount()));
|
||||
fund.setTotalWithdraw(fund.getTotalWithdraw().add(order.getAmount()));
|
||||
}
|
||||
|
||||
fund.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 更新账户
|
||||
LambdaUpdateWrapper<AccountFund> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(AccountFund::getUserId, order.getUserId())
|
||||
.set(AccountFund::getBalance, fund.getBalance())
|
||||
.set(AccountFund::getTotalDeposit, fund.getTotalDeposit())
|
||||
.set(AccountFund::getTotalWithdraw, fund.getTotalWithdraw())
|
||||
.set(AccountFund::getUpdateTime, LocalDateTime.now());
|
||||
assetService.updateFundAccount(updateWrapper);
|
||||
|
||||
// 记录流水
|
||||
int flowType = order.getType() == 1 ? 1 : 2;
|
||||
String remark = order.getType() == 1 ? "充值" : "提现";
|
||||
assetService.createFlow(order.getUserId(), flowType, order.getAmount(),
|
||||
fund.getBalance().subtract(order.getAmount()),
|
||||
fund.getBalance(), "USDT", orderNo, remark);
|
||||
|
||||
} else if (status == 3) {
|
||||
// 审批驳回
|
||||
if (rejectReason == null || rejectReason.isEmpty()) {
|
||||
throw new RuntimeException("请填写驳回原因");
|
||||
}
|
||||
order.setRejectReason(rejectReason);
|
||||
}
|
||||
|
||||
order.setStatus(status);
|
||||
order.setApproveAdminId(adminId);
|
||||
order.setApproveAdminName(adminName);
|
||||
order.setApproveTime(LocalDateTime.now());
|
||||
order.setAdminRemark(adminRemark);
|
||||
order.setUpdateTime(LocalDateTime.now());
|
||||
orderFundMapper.updateById(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待审批订单列表
|
||||
*/
|
||||
public IPage<OrderFund> getPendingOrders(int pageNum, int pageSize) {
|
||||
LambdaQueryWrapper<OrderFund> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(OrderFund::getStatus, 1)
|
||||
.orderByAsc(OrderFund::getCreateTime);
|
||||
|
||||
Page<OrderFund> page = new Page<>(pageNum, pageSize);
|
||||
return orderFundMapper.selectPage(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有充提订单(管理员)
|
||||
*/
|
||||
public IPage<OrderFund> getAllOrders(Integer type, Integer status, int pageNum, int pageSize) {
|
||||
LambdaQueryWrapper<OrderFund> wrapper = new LambdaQueryWrapper<>();
|
||||
if (type != null && type > 0) {
|
||||
wrapper.eq(OrderFund::getType, type);
|
||||
}
|
||||
if (status != null && status > 0) {
|
||||
wrapper.eq(OrderFund::getStatus, status);
|
||||
}
|
||||
wrapper.orderByDesc(OrderFund::getCreateTime);
|
||||
|
||||
Page<OrderFund> page = new Page<>(pageNum, pageSize);
|
||||
return orderFundMapper.selectPage(page, wrapper);
|
||||
}
|
||||
}
|
||||
189
src/main/java/com/it/rattan/monisuo/service/TradeService.java
Normal file
189
src/main/java/com/it/rattan/monisuo/service/TradeService.java
Normal file
@@ -0,0 +1,189 @@
|
||||
package com.it.rattan.monisuo.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.it.rattan.monisuo.entity.*;
|
||||
import com.it.rattan.monisuo.mapper.OrderTradeMapper;
|
||||
import com.it.rattan.monisuo.util.OrderNoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 交易服务
|
||||
*/
|
||||
@Service
|
||||
public class TradeService {
|
||||
|
||||
@Autowired
|
||||
private OrderTradeMapper orderTradeMapper;
|
||||
|
||||
@Autowired
|
||||
private AssetService assetService;
|
||||
|
||||
@Autowired
|
||||
private CoinService coinService;
|
||||
|
||||
/**
|
||||
* 买入
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> buy(Long userId, String coinCode, BigDecimal price, BigDecimal quantity) {
|
||||
Coin coin = coinService.getCoinByCode(coinCode);
|
||||
if (coin == null) {
|
||||
throw new RuntimeException("币种不存在");
|
||||
}
|
||||
|
||||
if (coin.getStatus() != 1) {
|
||||
throw new RuntimeException("该币种已下架");
|
||||
}
|
||||
|
||||
// 计算金额
|
||||
BigDecimal amount = price.multiply(quantity).setScale(8, RoundingMode.DOWN);
|
||||
|
||||
// 检查交易账户USDT余额
|
||||
AccountTrade usdtAccount = assetService.getOrCreateTradeAccount(userId, "USDT");
|
||||
if (usdtAccount.getQuantity().compareTo(amount) < 0) {
|
||||
throw new RuntimeException("交易账户USDT余额不足,请先划转资金");
|
||||
}
|
||||
|
||||
// 扣减USDT
|
||||
usdtAccount.setQuantity(usdtAccount.getQuantity().subtract(amount));
|
||||
usdtAccount.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 增加持仓
|
||||
AccountTrade coinAccount = assetService.getOrCreateTradeAccount(userId, coinCode);
|
||||
BigDecimal oldTotal = coinAccount.getQuantity().multiply(coinAccount.getAvgPrice());
|
||||
BigDecimal newTotal = oldTotal.add(amount);
|
||||
BigDecimal newQuantity = coinAccount.getQuantity().add(quantity);
|
||||
|
||||
coinAccount.setQuantity(newQuantity);
|
||||
if (newQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
coinAccount.setAvgPrice(newTotal.divide(newQuantity, 8, RoundingMode.DOWN));
|
||||
}
|
||||
coinAccount.setTotalBuy(coinAccount.getTotalBuy().add(quantity));
|
||||
coinAccount.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 创建订单
|
||||
OrderTrade order = new OrderTrade();
|
||||
order.setOrderNo(OrderNoUtil.tradeOrderNo());
|
||||
order.setUserId(userId);
|
||||
order.setCoinCode(coinCode.toUpperCase());
|
||||
order.setDirection(1); // 买入
|
||||
order.setOrderType(1); // 市价
|
||||
order.setPrice(price);
|
||||
order.setQuantity(quantity);
|
||||
order.setAmount(amount);
|
||||
order.setFee(BigDecimal.ZERO);
|
||||
order.setStatus(1); // 成功
|
||||
order.setCreateTime(LocalDateTime.now());
|
||||
orderTradeMapper.insert(order);
|
||||
|
||||
// 记录流水
|
||||
assetService.createFlow(userId, 5, amount.negate(), usdtAccount.getQuantity().add(amount),
|
||||
usdtAccount.getQuantity(), "USDT", order.getOrderNo(),
|
||||
"买入" + coinCode + ",数量:" + quantity);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orderNo", order.getOrderNo());
|
||||
result.put("price", price);
|
||||
result.put("quantity", quantity);
|
||||
result.put("amount", amount);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 卖出
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> sell(Long userId, String coinCode, BigDecimal price, BigDecimal quantity) {
|
||||
Coin coin = coinService.getCoinByCode(coinCode);
|
||||
if (coin == null) {
|
||||
throw new RuntimeException("币种不存在");
|
||||
}
|
||||
|
||||
if (coin.getStatus() != 1) {
|
||||
throw new RuntimeException("该币种已下架");
|
||||
}
|
||||
|
||||
// 检查持仓
|
||||
AccountTrade coinAccount = assetService.getOrCreateTradeAccount(userId, coinCode);
|
||||
if (coinAccount.getQuantity().compareTo(quantity) < 0) {
|
||||
throw new RuntimeException("持仓数量不足");
|
||||
}
|
||||
|
||||
// 计算金额
|
||||
BigDecimal amount = price.multiply(quantity).setScale(8, RoundingMode.DOWN);
|
||||
|
||||
// 扣减持仓
|
||||
coinAccount.setQuantity(coinAccount.getQuantity().subtract(quantity));
|
||||
coinAccount.setTotalSell(coinAccount.getTotalSell().add(quantity));
|
||||
coinAccount.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 增加USDT
|
||||
AccountTrade usdtAccount = assetService.getOrCreateTradeAccount(userId, "USDT");
|
||||
usdtAccount.setQuantity(usdtAccount.getQuantity().add(amount));
|
||||
usdtAccount.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 创建订单
|
||||
OrderTrade order = new OrderTrade();
|
||||
order.setOrderNo(OrderNoUtil.tradeOrderNo());
|
||||
order.setUserId(userId);
|
||||
order.setCoinCode(coinCode.toUpperCase());
|
||||
order.setDirection(2); // 卖出
|
||||
order.setOrderType(1); // 市价
|
||||
order.setPrice(price);
|
||||
order.setQuantity(quantity);
|
||||
order.setAmount(amount);
|
||||
order.setFee(BigDecimal.ZERO);
|
||||
order.setStatus(1); // 成功
|
||||
order.setCreateTime(LocalDateTime.now());
|
||||
orderTradeMapper.insert(order);
|
||||
|
||||
// 记录流水
|
||||
assetService.createFlow(userId, 6, amount, usdtAccount.getQuantity().subtract(amount),
|
||||
usdtAccount.getQuantity(), "USDT", order.getOrderNo(),
|
||||
"卖出" + coinCode + ",数量:" + quantity);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orderNo", order.getOrderNo());
|
||||
result.put("price", price);
|
||||
result.put("quantity", quantity);
|
||||
result.put("amount", amount);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取交易记录
|
||||
*/
|
||||
public IPage<OrderTrade> getOrders(Long userId, String coinCode, Integer direction,
|
||||
int pageNum, int pageSize) {
|
||||
LambdaQueryWrapper<OrderTrade> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(OrderTrade::getUserId, userId);
|
||||
if (coinCode != null && !coinCode.isEmpty()) {
|
||||
wrapper.eq(OrderTrade::getCoinCode, coinCode.toUpperCase());
|
||||
}
|
||||
if (direction != null && direction > 0) {
|
||||
wrapper.eq(OrderTrade::getDirection, direction);
|
||||
}
|
||||
wrapper.orderByDesc(OrderTrade::getCreateTime);
|
||||
|
||||
Page<OrderTrade> page = new Page<>(pageNum, pageSize);
|
||||
return orderTradeMapper.selectPage(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*/
|
||||
public OrderTrade getOrderDetail(Long userId, String orderNo) {
|
||||
LambdaQueryWrapper<OrderTrade> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(OrderTrade::getUserId, userId)
|
||||
.eq(OrderTrade::getOrderNo, orderNo);
|
||||
return orderTradeMapper.selectOne(wrapper);
|
||||
}
|
||||
}
|
||||
156
src/main/java/com/it/rattan/monisuo/service/UserService.java
Normal file
156
src/main/java/com/it/rattan/monisuo/service/UserService.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package com.it.rattan.monisuo.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.it.rattan.monisuo.context.UserContext;
|
||||
import com.it.rattan.monisuo.entity.AccountFund;
|
||||
import com.it.rattan.monisuo.entity.User;
|
||||
import com.it.rattan.monisuo.mapper.AccountFundMapper;
|
||||
import com.it.rattan.monisuo.mapper.UserMapper;
|
||||
import com.it.rattan.monisuo.util.JwtUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户服务
|
||||
*/
|
||||
@Service
|
||||
public class UserService extends ServiceImpl<UserMapper, User> {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private AccountFundMapper accountFundMapper;
|
||||
|
||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> register(String username, String password) {
|
||||
// 检查用户名是否存在
|
||||
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(User::getUsername, username);
|
||||
if (userMapper.selectCount(wrapper) > 0) {
|
||||
throw new RuntimeException("用户名已存在");
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
user.setNickname(username);
|
||||
user.setKycStatus(0);
|
||||
user.setStatus(1);
|
||||
user.setCreateTime(LocalDateTime.now());
|
||||
userMapper.insert(user);
|
||||
|
||||
// 初始化资金账户
|
||||
AccountFund fund = new AccountFund();
|
||||
fund.setUserId(user.getId());
|
||||
fund.setBalance(java.math.BigDecimal.ZERO);
|
||||
fund.setFrozen(java.math.BigDecimal.ZERO);
|
||||
fund.setTotalDeposit(java.math.BigDecimal.ZERO);
|
||||
fund.setTotalWithdraw(java.math.BigDecimal.ZERO);
|
||||
fund.setCreateTime(LocalDateTime.now());
|
||||
accountFundMapper.insert(fund);
|
||||
|
||||
// 生成Token
|
||||
String token = JwtUtil.createToken(user.getId(), username, "user");
|
||||
|
||||
// 更新Token
|
||||
user.setToken(token);
|
||||
userMapper.updateById(user);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
result.put("userInfo", buildUserInfo(user));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
public Map<String, Object> login(String username, String password) {
|
||||
// 查询用户
|
||||
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(User::getUsername, username);
|
||||
User user = userMapper.selectOne(wrapper);
|
||||
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
|
||||
if (user.getStatus() == 0) {
|
||||
throw new RuntimeException("账号已被禁用");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(password, user.getPassword())) {
|
||||
throw new RuntimeException("密码错误");
|
||||
}
|
||||
|
||||
// 生成新Token
|
||||
String token = JwtUtil.createToken(user.getId(), username, "user");
|
||||
|
||||
// 更新登录信息
|
||||
user.setToken(token);
|
||||
user.setLastLoginTime(LocalDateTime.now());
|
||||
userMapper.updateById(user);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
result.put("userInfo", buildUserInfo(user));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
public Map<String, Object> getUserInfo(Long userId) {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
return buildUserInfo(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传KYC资料
|
||||
*/
|
||||
@Transactional
|
||||
public void uploadKyc(Long userId, String idCardFront, String idCardBack) {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
|
||||
user.setIdCardFront(idCardFront);
|
||||
user.setIdCardBack(idCardBack);
|
||||
user.setKycStatus(1);
|
||||
user.setUpdateTime(LocalDateTime.now());
|
||||
userMapper.updateById(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户信息返回
|
||||
*/
|
||||
private Map<String, Object> buildUserInfo(User user) {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("id", user.getId());
|
||||
info.put("username", user.getUsername());
|
||||
info.put("nickname", user.getNickname());
|
||||
info.put("avatar", user.getAvatar());
|
||||
info.put("phone", user.getPhone());
|
||||
info.put("email", user.getEmail());
|
||||
info.put("kycStatus", user.getKycStatus());
|
||||
info.put("status", user.getStatus());
|
||||
return info;
|
||||
}
|
||||
}
|
||||
103
src/main/java/com/it/rattan/monisuo/util/JwtUtil.java
Normal file
103
src/main/java/com/it/rattan/monisuo/util/JwtUtil.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.it.rattan.monisuo.util;
|
||||
|
||||
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 java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
/** 密钥 */
|
||||
private static final String SECRET = "monisuo_jwt_secret_key_2024";
|
||||
/** 过期时间(7天) */
|
||||
private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L;
|
||||
/** 签发者 */
|
||||
private static final String ISSUER = "monisuo";
|
||||
|
||||
/**
|
||||
* 生成Token
|
||||
*/
|
||||
public static String createToken(Long userId, String username, String type) {
|
||||
Date now = new Date();
|
||||
Date expireDate = new Date(now.getTime() + EXPIRE_TIME);
|
||||
|
||||
Map<String, Object> header = new HashMap<>();
|
||||
header.put("alg", "HS256");
|
||||
header.put("typ", "JWT");
|
||||
|
||||
return JWT.create()
|
||||
.withHeader(header)
|
||||
.withIssuer(ISSUER)
|
||||
.withIssuedAt(now)
|
||||
.withExpiresAt(expireDate)
|
||||
.withClaim("userId", userId)
|
||||
.withClaim("username", username)
|
||||
.withClaim("type", type)
|
||||
.sign(Algorithm.HMAC256(SECRET));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token
|
||||
*/
|
||||
public static DecodedJWT verifyToken(String token) throws JWTVerificationException {
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
return verifier.verify(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*/
|
||||
public static Long getUserId(String token) {
|
||||
try {
|
||||
DecodedJWT jwt = verifyToken(token);
|
||||
return jwt.getClaim("userId").asLong();
|
||||
} catch (JWTVerificationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名
|
||||
*/
|
||||
public static String getUsername(String token) {
|
||||
try {
|
||||
DecodedJWT jwt = verifyToken(token);
|
||||
return jwt.getClaim("username").asString();
|
||||
} catch (JWTVerificationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型(user/admin)
|
||||
*/
|
||||
public static String getType(String token) {
|
||||
try {
|
||||
DecodedJWT jwt = verifyToken(token);
|
||||
return jwt.getClaim("type").asString();
|
||||
} catch (JWTVerificationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Token是否有效
|
||||
*/
|
||||
public static boolean isValid(String token) {
|
||||
try {
|
||||
verifyToken(token);
|
||||
return true;
|
||||
} catch (JWTVerificationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/main/java/com/it/rattan/monisuo/util/OrderNoUtil.java
Normal file
46
src/main/java/com/it/rattan/monisuo/util/OrderNoUtil.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.it.rattan.monisuo.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 订单号生成工具
|
||||
*/
|
||||
public class OrderNoUtil {
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
private static final AtomicLong SEQUENCE = new AtomicLong(0);
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
* 格式: 前缀 + 时间戳 + 6位序列号
|
||||
*/
|
||||
public static String generate(String prefix) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String timestamp = now.format(FORMATTER);
|
||||
long seq = SEQUENCE.getAndIncrement() % 1000000;
|
||||
return prefix + timestamp + String.format("%06d", seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成交易订单号
|
||||
*/
|
||||
public static String tradeOrderNo() {
|
||||
return generate("T");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成充提订单号
|
||||
*/
|
||||
public static String fundOrderNo() {
|
||||
return generate("F");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成流水号
|
||||
*/
|
||||
public static String flowNo() {
|
||||
return generate("L");
|
||||
}
|
||||
}
|
||||
41
src/main/java/com/it/rattan/rpc/BaseResponse.java
Normal file
41
src/main/java/com/it/rattan/rpc/BaseResponse.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.it.rattan.rpc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Getter
|
||||
@Setter
|
||||
public class BaseResponse {
|
||||
|
||||
public static final String SUCCESS = "0000";
|
||||
|
||||
public static final String FAIL = "0001";
|
||||
|
||||
private String msg;
|
||||
|
||||
private String code;
|
||||
|
||||
public BaseResponse(final String code,final String msg){
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public static BaseResponse success(){
|
||||
return new BaseResponse(BaseResponse.SUCCESS,"");
|
||||
}
|
||||
|
||||
public static BaseResponse fail(final String message){
|
||||
return new BaseResponse(BaseResponse.FAIL,message);
|
||||
}
|
||||
public static BaseResponse fail(final String code,final String message){
|
||||
return new BaseResponse(code,message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
31
src/main/java/com/it/rattan/rpc/ObjectRestResponse.java
Normal file
31
src/main/java/com/it/rattan/rpc/ObjectRestResponse.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.it.rattan.rpc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Getter
|
||||
@Setter
|
||||
public class ObjectRestResponse<T> extends BaseResponse {
|
||||
|
||||
private T Data;
|
||||
|
||||
public ObjectRestResponse(final T data){
|
||||
super(SUCCESS,"");
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
public ObjectRestResponse(final T data,final String code,final String msg){
|
||||
super(code,msg);
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
public ObjectRestResponse<T> data(T data){
|
||||
this.setData(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
35
src/main/java/com/it/rattan/rpc/RattanResponse.java
Normal file
35
src/main/java/com/it/rattan/rpc/RattanResponse.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.it.rattan.rpc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.it.rattan.enums.RattanMark;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Getter
|
||||
@Setter
|
||||
public class RattanResponse<T> extends ObjectRestResponse<T> {
|
||||
|
||||
|
||||
public RattanResponse(T data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public RattanResponse(T data, String code, String msg) {
|
||||
super(data, code, msg);
|
||||
}
|
||||
|
||||
public static RattanResponse<Object> success(){
|
||||
return new RattanResponse<>(RattanMark.NULL, SUCCESS,RattanMark.EMPTY);
|
||||
}
|
||||
|
||||
public static RattanResponse<Object> success(Object data){
|
||||
return new RattanResponse<>(data, SUCCESS,RattanMark.EMPTY);
|
||||
}
|
||||
|
||||
public static RattanResponse<Object> fail(String msg){
|
||||
return new RattanResponse<>(RattanMark.NULL, FAIL,msg);
|
||||
}
|
||||
}
|
||||
33
src/main/resources/application-dev.yml
Normal file
33
src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
server:
|
||||
port: 5010
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
username: monisuo
|
||||
password: JPJ8wYicSGC8aRnk
|
||||
url: jdbc:mysql://8.155.172.147:3306/monisuo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
|
||||
#mybatis-plus
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:com/it/rattan/monisuo/mapper/*.xml
|
||||
|
||||
bean-searcher:
|
||||
packages: com.it.rattan.monisuo
|
||||
params:
|
||||
pagination:
|
||||
start: 1
|
||||
ignore-case-key:
|
||||
|
||||
|
||||
# mybatisplus代码生成器配置
|
||||
generator:
|
||||
username: root
|
||||
password: 123admin
|
||||
driver: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/spccloud?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
|
||||
#dbTableList: #数据库的表,可多张(自己设置)
|
||||
#- rt_company
|
||||
prefix:
|
||||
rt
|
||||
58
src/main/resources/application-prd.yml
Normal file
58
src/main/resources/application-prd.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
server:
|
||||
port: 9010
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
username: root
|
||||
password: 897admin$$
|
||||
url: jdbc:mysql://47.97.10.240:3306/spccloud?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
initialSize: 1
|
||||
minIdle: 3
|
||||
maxActive: 80
|
||||
# 配置获取连接等待超时的时间
|
||||
maxWait: 60000
|
||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
minEvictableIdleTimeMillis: 30000
|
||||
validationQuery: select 'x'
|
||||
testWhileIdle: true
|
||||
testOnBorrow: false
|
||||
testOnReturn: false
|
||||
# 打开PSCache,并且指定每个连接上PSCache的大小
|
||||
poolPreparedStatements: true
|
||||
maxPoolPreparedStatementPerConnectionSize: 20
|
||||
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
|
||||
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
|
||||
|
||||
|
||||
#mybatis-plus
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:com.it.rattan.spccloud.mapper/*.xml
|
||||
|
||||
spccloud:
|
||||
pdf-download-path: C:/Users/Administrator/Desktop/temp/
|
||||
wkhtmltopdf-exe-path: C:/Users/Administrator/Desktop/wkhtmltopdf/bin/wkhtmltopdf.exe
|
||||
|
||||
|
||||
bean-searcher:
|
||||
packages: com.rattan.spccloud
|
||||
params:
|
||||
pagination:
|
||||
start: 1
|
||||
ignore-case-key:
|
||||
|
||||
|
||||
|
||||
# mybatisplus代码生成器配置
|
||||
#generator:
|
||||
#username: root
|
||||
#password: 123admin
|
||||
#driver: com.mysql.jdbc.Driver
|
||||
#url: jdbc:mysql://47.97.10.240:3306/spccloud?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
|
||||
#dbTableList: #数据库的表,可多张(自己设置)
|
||||
#- rt_company
|
||||
#prefix:
|
||||
#rt
|
||||
5
src/main/resources/application.yml
Normal file
5
src/main/resources/application.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
|
||||
16
src/main/resources/logback-spring.xml
Normal file
16
src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<contextName>logback</contextName>
|
||||
|
||||
<!-- 只输出到控制台 -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user