├── img.png ├── src ├── main │ ├── java │ │ └── com │ │ │ └── uuorb │ │ │ └── journal │ │ │ ├── model │ │ │ ├── kimi │ │ │ │ ├── RoleEnum.java │ │ │ │ └── KimiMessage.java │ │ │ ├── Config.java │ │ │ ├── EngelExpense.java │ │ │ ├── dto │ │ │ │ └── WechatLoginResp.java │ │ │ ├── LogBean.java │ │ │ ├── ActivityUserRel.java │ │ │ ├── AIConfig.java │ │ │ ├── Activity.java │ │ │ ├── User.java │ │ │ └── Expense.java │ │ │ ├── controller │ │ │ ├── query │ │ │ │ └── UserBean.java │ │ │ ├── vo │ │ │ │ ├── EngelResult.java │ │ │ │ ├── ChartsDataNode.java │ │ │ │ ├── CosCredential.java │ │ │ │ ├── BasePagination.java │ │ │ │ └── Result.java │ │ │ ├── TestController.java │ │ │ ├── SystemController.java │ │ │ ├── TencentController.java │ │ │ ├── ExpenseController.java │ │ │ ├── UserController.java │ │ │ ├── ActivityController.java │ │ │ ├── ChartsController.java │ │ │ └── AIController.java │ │ │ ├── constant │ │ │ ├── CacheConstant.java │ │ │ ├── JwtConstant.java │ │ │ └── ResultStatus.java │ │ │ ├── annotation │ │ │ ├── UserId.java │ │ │ ├── Admin.java │ │ │ ├── Authorization.java │ │ │ └── Log.java │ │ │ ├── mapper │ │ │ ├── LogMapper.java │ │ │ ├── SystemMapper.java │ │ │ ├── ChartsMapper.java │ │ │ ├── UserMapper.java │ │ │ ├── ExpenseMapper.java │ │ │ └── ActivityMapper.java │ │ │ ├── JournalApplication.java │ │ │ ├── config │ │ │ ├── WechatOpenConfig.java │ │ │ ├── TencentCloudConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── RedisConfig.java │ │ │ ├── exception │ │ │ └── CustomException.java │ │ │ ├── service │ │ │ ├── AiService.java │ │ │ ├── CosService.java │ │ │ ├── ExpenseService.java │ │ │ ├── ActivityService.java │ │ │ ├── KimiService.java │ │ │ ├── UserService.java │ │ │ └── CozeService.java │ │ │ ├── util │ │ │ ├── TokenSettings.java │ │ │ ├── IDUtil.java │ │ │ ├── IPUtil.java │ │ │ ├── SMSUtil.java │ │ │ ├── KimiUtils.java │ │ │ ├── TokenUtil.java │ │ │ └── RedisUtil.java │ │ │ ├── interceptor │ │ │ ├── UserIdResolver.java │ │ │ ├── AdminInterceptor.java │ │ │ └── AuthInterceptor.java │ │ │ ├── converter │ │ │ └── PositiveConverter.java │ │ │ └── aop │ │ │ └── LogAspect.java │ └── resources │ │ ├── application-test.yml │ │ ├── mapper │ │ ├── LogMapper.xml │ │ ├── ExpenseMapper.xml │ │ ├── ChartsMapper.xml │ │ ├── UserMapper.xml │ │ └── ActivityMapper.xml │ │ └── application.yml └── test │ └── java │ └── com │ └── uuorb │ └── journal │ └── JournalApplicationTests.java ├── .gitignore ├── README.md ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── pom.xml ├── mvnw.cmd └── mvnw /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyu610/journal-server/HEAD/img.png -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/kimi/RoleEnum.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model.kimi; 2 | 3 | public enum RoleEnum { 4 | system, 5 | user, 6 | assistant; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/Config.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Config { 7 | int id; 8 | String key; 9 | String value; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/EngelExpense.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EngelExpense { 7 | String type; 8 | double price; 9 | String label; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/query/UserBean.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller.query; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class UserBean { 9 | String userId; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/vo/EngelResult.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EngelResult { 7 | double value; 8 | String explain; 9 | String result; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/constant/CacheConstant.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.constant; 2 | 3 | public class CacheConstant { 4 | public final static String LOGIN_CODE = "journal::login_code::"; 5 | public final static String SEND_SMS_IP = "journal::sms_ip::"; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/annotation/UserId.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.annotation; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | @Target({ElementType.PARAMETER}) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | @Documented 9 | public @interface UserId { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/vo/ChartsDataNode.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.math.BigDecimal; 6 | 7 | @Data 8 | public class ChartsDataNode { 9 | String name; 10 | BigDecimal value; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/mapper/LogMapper.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.mapper; 2 | 3 | import com.uuorb.journal.model.LogBean; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | @Mapper 7 | public interface LogMapper { 8 | Integer insertLog(LogBean log); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/annotation/Admin.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Retention(RetentionPolicy.RUNTIME) 6 | @Target(ElementType.METHOD) 7 | @Documented 8 | public @interface Admin { 9 | String value() default ""; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/annotation/Authorization.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Target(ElementType.METHOD) 8 | @Documented 9 | public @interface Authorization { 10 | String value() default ""; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/uuorb/journal/JournalApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class JournalApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/kimi/KimiMessage.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model.kimi; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Data 11 | @Builder 12 | public class KimiMessage { 13 | 14 | private String role; 15 | 16 | private String content; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/vo/CosCredential.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller.vo; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | @Data 7 | @Accessors(chain = true) 8 | public class CosCredential { 9 | private String secretId; 10 | private String secretKey; 11 | private String sessionToken; 12 | private long startTime; 13 | private long expiredTime; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/JournalApplication.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JournalApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(JournalApplication.class, args); 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/annotation/Log.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.annotation; 2 | import java.lang.annotation.*; 3 | 4 | @Inherited 5 | @Target(ElementType.METHOD) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Documented 8 | public @interface Log { 9 | // POST GET PUT DELETE 来判断 10 | String logType() default "GET"; 11 | 12 | // 业务类型,根据Controller的名字来区分 13 | String actionType() default "none"; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/mapper/SystemMapper.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.mapper; 2 | 3 | import com.uuorb.journal.model.Config; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Select; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface SystemMapper { 11 | @Select("SELECT * FROM system_config WHERE user_id = 'system'") 12 | List getAllSystemConfig(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/config/WechatOpenConfig.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.config; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Setter 9 | @Getter 10 | @Component 11 | @ConfigurationProperties(prefix = "tencent.wechat.open") 12 | public class WechatOpenConfig { 13 | private String secret; 14 | private String appid; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/exception/CustomException.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.exception; 2 | 3 | import com.uuorb.journal.constant.ResultStatus; 4 | 5 | public class CustomException extends Exception { 6 | 7 | private final ResultStatus customErrEnum; 8 | 9 | public CustomException(ResultStatus customErrEnum) { 10 | this.customErrEnum = customErrEnum; 11 | } 12 | 13 | public ResultStatus getCustomErrEnum() { 14 | return customErrEnum; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/mapper/ChartsMapper.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.mapper; 2 | 3 | import com.uuorb.journal.controller.vo.ChartsDataNode; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | import java.util.List; 7 | 8 | @Mapper 9 | public interface ChartsMapper { 10 | 11 | List queryWeekly(String activityId); 12 | List queryWeeklyIncome(String activityId); 13 | 14 | List queryGroupByType(String activityId); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/dto/WechatLoginResp.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serial; 6 | import java.io.Serializable; 7 | @Data 8 | public class WechatLoginResp implements Serializable { 9 | 10 | @Serial 11 | private static final long serialVersionUID = 3799784124974560066L; 12 | 13 | private String openid; 14 | private String session_key; 15 | private String unionid; 16 | private String errcode; 17 | private String errmsg; 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import com.uuorb.journal.controller.vo.Result; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | @RequestMapping("/system") 10 | public class TestController { 11 | @GetMapping("/version") 12 | Result getVersion() { 13 | return Result.ok("0.0.1"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/LogBean.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.Date; 8 | 9 | @Data 10 | @Builder 11 | @AllArgsConstructor 12 | public class LogBean { 13 | 14 | Integer id; 15 | 16 | String userID; 17 | 18 | Date createTime; 19 | 20 | Long duration; 21 | 22 | String httpMethod; 23 | 24 | String params; 25 | 26 | String functionName; 27 | 28 | String url; 29 | 30 | String ip; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/ActivityUserRel.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Date; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class ActivityUserRel { 16 | 17 | String activityId; 18 | 19 | String userId; 20 | 21 | Integer id; 22 | 23 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 24 | private Date createTime; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/AIConfig.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class AIConfig { 12 | 13 | String openingStatement; 14 | 15 | // 称呼 16 | String salutation; 17 | 18 | // 关系 19 | String relationship; 20 | 21 | // 性格 22 | String personality; 23 | 24 | String lastLoginTime; 25 | 26 | public static AIConfig of(User user) { 27 | return BeanUtil.copyProperties(user, AIConfig.class); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | username: username 4 | url: jdbc:mysql://[url:port]/journal?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true 5 | password: password 6 | jwt: 7 | secretKey: secretKey 8 | 9 | kimi: 10 | key: kimiKey 11 | 12 | coze: 13 | token: cozeToken 14 | 15 | apple: 16 | key: appleKey 17 | 18 | tencent: 19 | sms: 20 | secretId: secretId 21 | secretKey: secretKey 22 | cloud: 23 | # 腾讯云cos 24 | secretId: secretId 25 | secretKey: secretKey 26 | region: ap-beijing 27 | bucket: bucket 28 | allowPrefix: "journal*" 29 | wechat: 30 | open: 31 | appid: appid 32 | secret: secret 33 | -------------------------------------------------------------------------------- /src/main/resources/mapper/LogMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | insert into user_log(user_id, 6 | create_time, 7 | `duration`, 8 | http_method, 9 | params, 10 | function_name, 11 | url, 12 | ip) 13 | values (#{userID}, #{createTime}, #{duration}, #{httpMethod}, #{params}, #{functionName}, #{url}, #{ip}) 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.mapper; 2 | 3 | import com.uuorb.journal.model.User; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Select; 6 | import org.apache.ibatis.annotations.Update; 7 | 8 | import java.util.List; 9 | 10 | @Mapper 11 | public interface UserMapper { 12 | 13 | @Select("SELECT * FROM users where user_id = #{userId}") 14 | User getUserByUserId(String userId); 15 | 16 | /** 17 | * 慎用!!! 没有判空,可能会全表查 18 | * 19 | * @param telephone 20 | * @return 21 | */ 22 | List selectUser(User query); 23 | 24 | void createUser(User user); 25 | 26 | void updateUser(User user); 27 | @Update("UPDATE users SET current_activity_id = null WHERE user_id = #{userId}") 28 | void setCurrentActivityNull(String userId); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/SystemController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import com.uuorb.journal.controller.vo.Result; 4 | import com.uuorb.journal.mapper.SystemMapper; 5 | import com.uuorb.journal.model.Config; 6 | import jakarta.annotation.Resource; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/system") 15 | public class SystemController { 16 | @Resource 17 | SystemMapper systemMapper; 18 | 19 | @GetMapping("/config/all") 20 | Result getAllConfig(){ 21 | List allSystemConfig = systemMapper.getAllSystemConfig(); 22 | return Result.ok(allSystemConfig); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 好享记账后端 2 | ### 视频介绍 3 | https://www.bilibili.com/video/BV1WbGBzcEec 4 | ### 如有疑问,可与我联系 5 | uuorb@foxmail.com 6 | 7 | 8 | - 项目链接 9 | 1. 前端:https://github.com/suyu610/journal-flutter 10 | 2. 后端:https://github.com/suyu610/journal-server 11 | - 视频介绍:https://www.bilibili.com/video/BV1WbGBzcEec 12 | - 项目体验:[AppStore](https://apps.apple.com/cn/app/%E5%A5%BD%E4%BA%AB%E8%AE%B0%E8%B4%A6/id6736673372) 13 | ## 项目简介 14 | 15 | `好享记账`是一个基于flutter开发的,跨端AI记账APP。实现了多人记账、语音记账、截屏快捷记账、自然语言记账等功能。 16 | 整体功能完整,前后端全开源。 17 | 18 | ## 技术栈 19 | 前端使用flutter、后端使用java、mysql、redis 20 | 21 | ## 项目截图 22 | | 首页 | 对话页 | 23 | | --- | --- | 24 | | ![Image](https://github.com/user-attachments/assets/e336bc09-a076-443f-bbfe-70de0109ab9f) |![Image](https://github.com/user-attachments/assets/ddfb3850-7270-462b-b4f2-dfc7959ed16b) 25 | | 语音记账 | - | 26 | | ![Image](https://github.com/user-attachments/assets/5a656e06-35dc-4c09-b47d-33e78cacc1b0) | | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/mapper/ExpenseMapper.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.mapper; 2 | 3 | import com.uuorb.journal.model.EngelExpense; 4 | import com.uuorb.journal.model.Expense; 5 | import org.apache.ibatis.annotations.Delete; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Select; 8 | 9 | import java.util.List; 10 | 11 | @Mapper 12 | public interface ExpenseMapper { 13 | 14 | List queryList(String activityId); 15 | 16 | List queryListBrief(String activityId); 17 | Integer insert(Expense expense); 18 | 19 | void update(Expense expense); 20 | @Select("SELECT COUNT(*) FROM expense WHERE user_id = #{userId} AND expense_id = #{expenseId}") 21 | Integer count(Expense expense); 22 | 23 | Expense queryById(String expenseId); 24 | @Delete("DELETE FROM expense WHERE expense_id = #{expenseId}") 25 | void delete(String expenseId); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/AiService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import com.uuorb.journal.model.AIConfig; 4 | import com.uuorb.journal.model.EngelExpense; 5 | import com.uuorb.journal.model.Expense; 6 | 7 | import java.io.OutputStream; 8 | import java.util.List; 9 | 10 | public interface AiService { 11 | 12 | final String KIMI_MODEL = "moonshot-v1-8k"; 13 | 14 | Expense formatExpense(String naturalSentence); 15 | 16 | String praise(String sentence); 17 | 18 | String praise(String sentence, AIConfig config); 19 | 20 | String greet(AIConfig aiConfig); 21 | 22 | String generateImage(String model, String description, String role); 23 | 24 | void praiseStream(String sentence, AIConfig config, OutputStream outputStream); 25 | 26 | String tts(String sentence, AIConfig aiConfig); 27 | 28 | void engelExplain(List expenseList, OutputStream outputStream); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/TokenSettings.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import lombok.Data; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.time.Duration; 10 | 11 | 12 | @Component 13 | @Data 14 | public class TokenSettings { 15 | @Value("${jwt.secretKey:}") 16 | private String secretKey; 17 | 18 | @Value("${jwt.accessTokenExpireTime:}") 19 | private Duration accessTokenExpireTime; 20 | 21 | @Value("${jwt.refreshTokenExpireTime:}") 22 | private Duration refreshTokenExpireTime; 23 | 24 | @Value("${jwt.refreshTokenExpireAppTime:}") 25 | private Duration refreshTokenExpireAppTime; 26 | 27 | @Value("${jwt.issuer:}") 28 | private String issuer; 29 | 30 | @PostConstruct 31 | public void init() { 32 | TokenUtil.setTokenSettings(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/constant/JwtConstant.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.constant; 2 | 3 | public class JwtConstant { 4 | /** 5 | * 通用jwt subject 6 | */ 7 | public static final String GEN_JWT_SUBJECT = "gen_token"; 8 | 9 | public static final String WEAPP_JWT_SUBJECT = "weapp_token"; 10 | /** 11 | * 用户名称 key 12 | */ 13 | public static final String JWT_USER_OPENID = "jwt-user-openid"; 14 | public static final String JWT_USER_ID = "jwt-user-id"; 15 | 16 | public static final String JWT_USER_SYSTEM = "jwt-user-system"; 17 | 18 | 19 | /** 20 | * 角色信息key 21 | */ 22 | public static final String ROLES_INFOS_KEY = "roles-infos-key"; 23 | 24 | /** 25 | * 管理员身份key 26 | */ 27 | public static final String ROLES_ADMIN_VALUE = "roles-admin"; 28 | 29 | /** 30 | * 普通用户身份key 31 | */ 32 | public static final String ROLES_USER_VALUE = "roles-user"; 33 | 34 | /** 35 | * vip身份key 36 | */ 37 | public static final String ROLES_VIP_VALUE = "roles-vip"; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/config/TencentCloudConfig.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.config; 2 | 3 | import lombok.Getter; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Getter 8 | @Component 9 | @ConfigurationProperties(prefix = "tencent.cloud") 10 | public class TencentCloudConfig { 11 | private String secretId; 12 | private String secretKey; 13 | private String bucket; 14 | private String region; 15 | private String allowPrefix; 16 | 17 | 18 | public void setSecretId(String secretId) { 19 | this.secretId = secretId; 20 | } 21 | 22 | public void setSecretKey(String secretKey) { 23 | this.secretKey = secretKey; 24 | } 25 | 26 | public void setBucket(String bucket) { 27 | this.bucket = bucket; 28 | } 29 | 30 | public void setRegion(String region) { 31 | this.region = region; 32 | } 33 | 34 | public void setAllowPrefix(String allowPrefix) { 35 | this.allowPrefix = allowPrefix; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/TencentController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import com.uuorb.journal.annotation.Authorization; 4 | import com.uuorb.journal.annotation.Log; 5 | import com.uuorb.journal.controller.vo.CosCredential; 6 | import com.uuorb.journal.controller.vo.Result; 7 | import com.uuorb.journal.service.CosService; 8 | import jakarta.annotation.Resource; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @Slf4j 15 | @RestController 16 | @RequestMapping("/tencent") 17 | public class TencentController { 18 | 19 | 20 | @Resource 21 | private CosService cosService; 22 | @Log 23 | 24 | @Authorization 25 | @GetMapping("/cos/credential") 26 | public Result getCredential() { 27 | CosCredential credential = cosService.getCredential(); 28 | log.info("获取文件上传密钥:{}", credential.toString()); 29 | return Result.ok(credential); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/vo/BasePagination.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller.vo; 2 | 3 | import com.github.pagehelper.PageInfo; 4 | import lombok.Data; 5 | 6 | import java.io.Serial; 7 | import java.io.Serializable; 8 | 9 | @Data 10 | public class BasePagination implements Serializable { 11 | 12 | @Serial 13 | private static final long serialVersionUID = 7130845630609910943L; 14 | 15 | int totalCount; 16 | 17 | int totalPage; 18 | 19 | int pageSize; 20 | 21 | Boolean hasNextPage; 22 | 23 | int currentPage; 24 | 25 | T data; 26 | 27 | public static BasePagination createFromPageInfo(PageInfo pageInfo) { 28 | BasePagination basePagination = new BasePagination(); 29 | basePagination.setCurrentPage(pageInfo.getPageNum()); 30 | basePagination.setPageSize(pageInfo.getPageSize()); 31 | basePagination.setTotalPage(pageInfo.getPages()); 32 | basePagination.setHasNextPage(pageInfo.isHasNextPage()); 33 | basePagination.setTotalCount((int) pageInfo.getTotal()); 34 | basePagination.setData(pageInfo.getList()); 35 | return basePagination; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/Activity.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.math.BigDecimal; 10 | import java.util.Date; 11 | import java.util.List; 12 | 13 | @Data 14 | @Builder 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class Activity { 18 | private Integer id; 19 | private List expenseList; 20 | String creatorName; 21 | String creatorUserId; 22 | private String activityName; 23 | private BigDecimal budget; 24 | private String userId; 25 | 26 | private User user; 27 | 28 | private BigDecimal remainingBudget; 29 | private BigDecimal totalExpense; 30 | 31 | private BigDecimal totalIncome; 32 | 33 | private Boolean activated; 34 | 35 | private String activityId; 36 | 37 | private List userList; 38 | 39 | 40 | 41 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 42 | private Date createTime; 43 | 44 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 45 | private Date updateTime; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 5666 3 | servlet: 4 | context-path: /api 5 | spring: 6 | application: 7 | name: journal 8 | profiles: 9 | active: test 10 | data: 11 | redis: 12 | database: 3 13 | host: 127.0.0.1 14 | timeout: 20000 15 | port: 6379 16 | jedis: 17 | pool: 18 | max-active: 1000 19 | max-wait: 100s 20 | max-idle: 1000 21 | min-idle: 1000 22 | 23 | jwt: 24 | accessTokenExpireTime: P30D 25 | refreshTokenExpireAppTime: P30D 26 | refreshTokenExpireTime: P30D 27 | issuer: com.uuorb.journal 28 | 29 | mybatis: 30 | mapper-locations: classpath*:mapper/*Mapper.xml 31 | type-aliases-package: com.uuorb.journal 32 | configuration-properties: 33 | org.apache.ibatis.parsing.PropertyParser.enable-default-value: true 34 | configuration: 35 | use-generated-keys: true 36 | # 转驼峰 37 | map-underscore-to-camel-case: true 38 | log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl 39 | 40 | logging: 41 | level: 42 | com.uuorb.journal: 43 | mapper: error 44 | 45 | # 分页配置 46 | pagehelper: 47 | helper-dialect: mysql 48 | reasonable: true 49 | support-methods-arguments: true 50 | params: count=countSql 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/interceptor/UserIdResolver.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.interceptor; 2 | 3 | import com.uuorb.journal.annotation.UserId; 4 | import com.uuorb.journal.util.TokenUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.bind.support.WebDataBinderFactory; 9 | import org.springframework.web.context.request.NativeWebRequest; 10 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 11 | import org.springframework.web.method.support.ModelAndViewContainer; 12 | 13 | @Component 14 | @Slf4j 15 | public class UserIdResolver implements HandlerMethodArgumentResolver { 16 | public UserIdResolver() { 17 | } 18 | 19 | public boolean supportsParameter(MethodParameter parameter) { 20 | return parameter.hasParameterAnnotation(UserId.class); 21 | } 22 | 23 | public String resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { 24 | if (parameter.getParameterAnnotation(UserId.class) != null) { 25 | String token = webRequest.getHeader("Authorization"); 26 | return TokenUtil.getUserId(token); 27 | } 28 | return null; 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/User.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Date; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class User { 16 | 17 | Integer id; 18 | 19 | String userId; 20 | 21 | String nickname; 22 | 23 | String avatarUrl; 24 | 25 | String openid; 26 | 27 | String unionId; 28 | String appleId; 29 | 30 | Boolean vip; 31 | 32 | String telephone; 33 | 34 | String currentActivityId; 35 | 36 | // 开场白 37 | String openingStatement; 38 | 39 | 40 | String aiAvatarUrl; 41 | 42 | // 称呼 43 | String salutation; 44 | 45 | // 关系 46 | String relationship; 47 | 48 | // 性格 49 | String personality; 50 | 51 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 52 | Date createTime; 53 | 54 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 55 | Date updateTime; 56 | 57 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 58 | Date vipExpireTime; 59 | 60 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 61 | Date lastLoginTime; 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.config; 2 | 3 | import com.uuorb.journal.annotation.UserId; 4 | import com.uuorb.journal.interceptor.AdminInterceptor; 5 | import com.uuorb.journal.interceptor.AuthInterceptor; 6 | import com.uuorb.journal.interceptor.UserIdResolver; 7 | import jakarta.annotation.Resource; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 11 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 12 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 13 | 14 | import java.util.List; 15 | 16 | @Configuration 17 | 18 | public class SecurityConfig implements WebMvcConfigurer { 19 | 20 | @Resource 21 | private AuthInterceptor authInterceptor; 22 | 23 | @Resource 24 | private AdminInterceptor adminInterceptor; 25 | 26 | @Resource 27 | private UserIdResolver userIdResolver; 28 | 29 | @Override 30 | public void addInterceptors(InterceptorRegistry registry) { 31 | registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns("/token").excludePathPatterns("/user/login"); 32 | registry.addInterceptor(adminInterceptor).addPathPatterns("/**"); 33 | } 34 | 35 | 36 | @Override 37 | public void addArgumentResolvers(final List resolvers) { 38 | resolvers.add(userIdResolver); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/converter/PositiveConverter.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.converter; 2 | 3 | import com.alibaba.excel.converters.Converter; 4 | import com.alibaba.excel.enums.CellDataTypeEnum; 5 | import com.alibaba.excel.metadata.GlobalConfiguration; 6 | import com.alibaba.excel.metadata.data.ReadCellData; 7 | import com.alibaba.excel.metadata.data.WriteCellData; 8 | import com.alibaba.excel.metadata.property.ExcelContentProperty; 9 | 10 | /** 11 | * easyExcel所有到的converter 12 | */ 13 | public class PositiveConverter implements Converter { 14 | 15 | @Override 16 | public Class supportJavaTypeKey() { 17 | return Integer.class; 18 | } 19 | 20 | @Override 21 | public CellDataTypeEnum supportExcelTypeKey() { 22 | return CellDataTypeEnum.STRING; 23 | } 24 | 25 | @Override 26 | public Integer convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, 27 | GlobalConfiguration globalConfiguration) { 28 | if ("是".equals(cellData.getStringValue())) { 29 | return 1; 30 | } 31 | if ("否".equals(cellData.getStringValue())) { 32 | return 0; 33 | } else { 34 | return 416; 35 | } 36 | } 37 | 38 | @Override 39 | public WriteCellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, 40 | GlobalConfiguration globalConfiguration) throws Exception { 41 | if (value.equals(0)) { 42 | return new WriteCellData<>("支出"); 43 | } 44 | if (value.equals(1)) { 45 | return new WriteCellData<>("收入"); 46 | } 47 | 48 | return new WriteCellData<>("未知"); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/vo/Result.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller.vo; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import com.uuorb.journal.constant.ResultStatus; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serial; 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @ClassName Result 14 | * @Description 统一返回值 15 | * @Author uuorb 16 | * @Date 2021/5/18 12:54 下午 17 | * @Version 0.1 18 | **/ 19 | 20 | @Data 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | public class Result implements Serializable { 24 | 25 | @Serial 26 | private static final long serialVersionUID = -2289349812892813717L; 27 | 28 | int code; 29 | 30 | String msg; 31 | 32 | T data; 33 | 34 | public static Result ok(T data) { 35 | return new Result(0, "success", data); 36 | } 37 | 38 | public static Result ok() { 39 | return new Result(0, "success", null); 40 | } 41 | 42 | public static Result error() { 43 | JSONObject resultDate = new JSONObject(); 44 | resultDate.putOnce("message", "出现未知错误,请稍后再试"); 45 | return new Result(-1, "fail", resultDate); 46 | } 47 | 48 | public static Result error(int errcode, String errmsg) { 49 | return new Result(errcode, errmsg, null); 50 | } 51 | 52 | public static Result error(ResultStatus resultStatusEnum) { 53 | return new Result(resultStatusEnum.getCode(), resultStatusEnum.getMsg(), null); 54 | } 55 | 56 | // 处理参数校验规则的错误 57 | public static Result paramValidError(String errmsg) { 58 | return new Result(-400, errmsg, null); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/mapper/ActivityMapper.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.mapper; 2 | 3 | import com.uuorb.journal.model.Activity; 4 | import com.uuorb.journal.model.ActivityUserRel; 5 | import org.apache.ibatis.annotations.Delete; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | import org.apache.ibatis.annotations.Update; 9 | 10 | import java.util.List; 11 | 12 | @Mapper 13 | public interface ActivityMapper { 14 | List querySelfActivityList(Activity activity); 15 | List queryJoinedActivityList(Activity activity); 16 | 17 | void insert(Activity activity); 18 | 19 | void updateAllInActive(@Param("activityId") String id, @Param("userId") String userId); 20 | 21 | Integer relCount(ActivityUserRel query); 22 | 23 | Boolean isOwner(Activity query); 24 | 25 | Integer updateActivity(Activity activity); 26 | 27 | Activity queryActivityByActivityId(String activityId); 28 | 29 | void joinActivity(ActivityUserRel joinQuery); 30 | 31 | boolean isJoinedOwner(Activity activity); 32 | 33 | void refreshActivityRemainingBudget(String activityId); 34 | 35 | Activity getCurrentActivity(@Param("userId") String userId); 36 | 37 | @Delete("DELETE FROM activity_user_rel WHERE activity_id = #{activityId}") 38 | void deleteAllRef(String activityId); 39 | 40 | @Delete("DELETE FROM activity WHERE activity_id = #{activityId}") 41 | void deleteActivity(String activityId); 42 | 43 | @Update("UPDATE users SET current_activity_id = null WHERE current_activity_id = #{activityId}") 44 | void deleteAllUserActivityRef(String activityId); 45 | 46 | 47 | @Delete("DELETE FROM activity_user_rel WHERE activity_id = #{activityId} AND user_id = #{userId}") 48 | void exitActivity(String activityId, String userId); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/model/Expense.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.model; 2 | 3 | import com.alibaba.excel.annotation.ExcelIgnore; 4 | import com.alibaba.excel.annotation.ExcelProperty; 5 | import com.alibaba.excel.annotation.format.DateTimeFormat; 6 | import com.alibaba.excel.annotation.write.style.ColumnWidth; 7 | import com.fasterxml.jackson.annotation.JsonFormat; 8 | import com.uuorb.journal.converter.PositiveConverter; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | 14 | import java.util.Date; 15 | 16 | @Data 17 | @Builder 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | public class Expense { 21 | @ExcelIgnore 22 | Integer id; 23 | 24 | @ExcelIgnore 25 | String expenseId; 26 | 27 | @ExcelProperty(value = "分类", index = 1) 28 | String type; 29 | 30 | @ExcelProperty(value = "金额") 31 | double price; 32 | 33 | @ColumnWidth(60) 34 | @ExcelProperty("备注") 35 | String label; 36 | 37 | @ExcelProperty(value = "方向", converter = PositiveConverter.class, index = 0) 38 | Integer positive; 39 | 40 | @ExcelIgnore 41 | String userId; 42 | @ColumnWidth(20) 43 | @ExcelProperty("记录者") 44 | String userNickname; 45 | 46 | @ExcelIgnore 47 | String userAvatar; 48 | 49 | @ExcelIgnore 50 | String activityId; 51 | 52 | @ExcelProperty("创建时间") 53 | @ColumnWidth(20) 54 | @DateTimeFormat("yyyy-MM-dd") 55 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 56 | Date createTime; 57 | 58 | @ExcelProperty("账单日期") 59 | @ColumnWidth(20) 60 | @DateTimeFormat("yyyy-MM-dd") 61 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 62 | Date expenseTime; 63 | 64 | 65 | 66 | @ExcelIgnore 67 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 68 | Date updateTime; 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/CosService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import com.uuorb.journal.config.TencentCloudConfig; 4 | import com.uuorb.journal.controller.vo.CosCredential; 5 | import jakarta.annotation.Resource; 6 | import org.springframework.stereotype.Service; 7 | import com.tencent.cloud.CosStsClient; 8 | import com.tencent.cloud.Response; 9 | 10 | import java.io.IOException; 11 | import java.util.TreeMap; 12 | 13 | @Service 14 | public class CosService { 15 | @Resource 16 | private TencentCloudConfig tencentCloudConfig; 17 | 18 | public CosCredential getCredential() { 19 | 20 | TreeMap config = new TreeMap<>(); 21 | config.put("secretId", tencentCloudConfig.getSecretId()); 22 | config.put("secretKey", tencentCloudConfig.getSecretKey()); 23 | 24 | config.put("durationSeconds", 180); 25 | config.put("bucket", tencentCloudConfig.getBucket()); 26 | config.put("region", tencentCloudConfig.getRegion()); 27 | 28 | config.put("allowPrefixes", new String[] {tencentCloudConfig.getAllowPrefix()}); 29 | 30 | config.put("allowActions", 31 | new String[] {"name/cos:PutObject", "name/cos:PostObject", 32 | "name/cos:InitiateMultipartUpload", "name/cos:ListMultipartUploads", 33 | "name/cos:ListParts", "name/cos:UploadPart", "name/cos:CompleteMultipartUpload"}); 34 | 35 | try { 36 | Response response = CosStsClient.getCredential(config); 37 | return new CosCredential().setSecretId(response.credentials.tmpSecretId) 38 | .setSecretKey(response.credentials.tmpSecretKey) 39 | .setSessionToken(response.credentials.sessionToken) 40 | .setStartTime(response.startTime).setExpiredTime(response.expiredTime); 41 | } catch (IOException e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/constant/ResultStatus.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.constant; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum ResultStatus { 7 | SUCCESS_STATUS(0, "SUCCESS"), 8 | 9 | //////////////////// 客户端错误 ///////////////////////// 10 | TOKEN_VALID(-1, "TOKEN失效"), 11 | PARAM_VALID(401, "参数错误"), 12 | PARAM_MISS(402, "参数缺失或格式错误,多见于末尾有多余逗号"), 13 | PARAM_TOO_LONG(403, "参数太多"), 14 | RESOURCE_NOT_FOUND(404, "资源未找到"), 15 | AI_FORMAT_ERROR(405, "信息提取失败"), 16 | 17 | NOT_OWN_RESOURCE(405, "无资源访问权限,或不存在"), 18 | DONT_MODIFY_OFFICIAL_BOOK(406, "不能修改官方词书"), 19 | 20 | ACTION_REPEAT(407, "重复点赞或取消"), 21 | DELETE_DEFAULT_GROUP(408, "不能删除默认分组"), 22 | 23 | VIP_PERMISSION(409, "VIP权限不足"), 24 | OVER_MAX_COUNT(410, "容量已达上限,请解锁会员后重试"), 25 | NOT_SUBSCRIBE(411, "请先关注公众号"), 26 | BOOK_END(412, "词书已背完"), 27 | FILE_TOO_LARGE(413, "文件过大"), 28 | VERIFY_CODE_LIMITED(414, "验证码发送过于频繁,请稍后再试"), 29 | VERIFY_CODE_ERROR(415, "验证码错误"), 30 | TELEPHONE_ERROR(416, "手机号不符合规范"), 31 | PAYMENT_INDEX_NULL(417, "未选择支付方式"), 32 | PAYMENT_INDEX_INVALID(418, "支付参数错误"), 33 | PAYMENT_NOT_PAID(419, "支付未完成"), 34 | OPERATION_ERROR(420, "微信支付操作失败"), 35 | ORDER_NO_NOT_EXIST(420, "订单不存在"), 36 | IOS_SECRET_INVALID(421, "苹果支付验证失败"), 37 | PUNCH_ALREADY_PUNCHED(422, "今日已打卡"), 38 | PUNCH_ALREADY_BOUGHT(423, "不要重复购买"), 39 | NOT_ENOUGH_POINT(424, "积分不足"), 40 | 41 | ///////////////////// 服务端错误 //////////////////////// 42 | GENERAL_ERROR(500, "系统错误"), 43 | 44 | GET_OUT(-999, "非法攻击"), 45 | 46 | IOS_JWT_INVALID(421, "苹果登录失败"), 47 | PRIMARY_ID_MISS(422, "未传递主键"), 48 | FORBID_JOIN_SELF_ACTIVITY(423, "不允许加入自己的活动"), 49 | JOIN_REPEAT(424, "已经加入该活动"), OWNER_CANT_EXIT(425,"创建者只能删除账本" ); 50 | 51 | ResultStatus(Integer code, String msg) { 52 | this.code = code; 53 | this.msg = msg; 54 | } 55 | 56 | private Integer code; 57 | 58 | private String msg; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/IDUtil.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | import java.util.UUID; 4 | 5 | public class IDUtil { 6 | 7 | private static final String ACTIVITY_PREDIX = "ac"; 8 | 9 | private static final String USER_PREFIX = "us"; 10 | 11 | private static final String MESSAGE_PREFIX = "ms"; 12 | 13 | private static final String ORDER_PREFIX = "od"; 14 | 15 | private static final String SMS_PREFIX = "sms"; 16 | 17 | private static final String EXPENSE_PREFIX = "ex"; 18 | 19 | public static String random(int length) { 20 | return UUID.randomUUID().toString().replace("-", "").substring(0, length).toLowerCase(); 21 | } 22 | 23 | public static String activityId() { 24 | return ACTIVITY_PREDIX + random(16); 25 | } 26 | 27 | public static String userId() { 28 | return USER_PREFIX + random(16); 29 | } 30 | 31 | public static String expenseId() { 32 | return EXPENSE_PREFIX + random(16); 33 | } 34 | 35 | public static String msgID() { 36 | return MESSAGE_PREFIX + random(16); 37 | } 38 | 39 | public static String getHashIdByUserId(String userId1, String userId2) { 40 | int hashCode1 = userId1.hashCode(); 41 | int hashCode2 = userId2.hashCode(); 42 | 43 | if (hashCode1 > hashCode2) { 44 | return String.format("%08x", Math.abs(hashCode1)) + String.format("%08x", Math.abs(hashCode2)); 45 | } else { 46 | return String.format("%08x", Math.abs(hashCode2)) + String.format("%08x", Math.abs(hashCode1)); 47 | } 48 | } 49 | 50 | public static String orderId() { 51 | return ORDER_PREFIX + random(16); 52 | } 53 | 54 | public static String smsId() { 55 | return SMS_PREFIX + random(15); 56 | } 57 | 58 | public static String generateUUID() { 59 | String uuidString = UUID.randomUUID().toString().replace("-", ""); 60 | return uuidString.length() > 32 ? uuidString.substring(0, 32) : uuidString; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/interceptor/AdminInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.interceptor; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.uuorb.journal.annotation.Admin; 5 | import com.uuorb.journal.controller.vo.Result; 6 | import com.uuorb.journal.util.TokenUtil; 7 | import io.netty.util.internal.StringUtil; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | import jakarta.servlet.http.HttpServletResponse; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.web.method.HandlerMethod; 12 | import org.springframework.web.servlet.HandlerInterceptor; 13 | 14 | import java.io.OutputStream; 15 | import java.nio.charset.StandardCharsets; 16 | 17 | /** 18 | * 必须是管理员才能访问的接口 19 | */ 20 | 21 | @Service 22 | public class AdminInterceptor implements HandlerInterceptor { 23 | @Override 24 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 25 | Object handler) throws Exception { 26 | if (handler instanceof HandlerMethod) { 27 | HandlerMethod hm = (HandlerMethod) handler; 28 | Admin signature = hm.getMethodAnnotation(Admin.class); 29 | if (signature == null) { 30 | return true; 31 | } 32 | 33 | // 验证签名的方法 34 | String token = request.getHeader("Authorization"); 35 | 36 | // token存在且 role=admin 37 | if(!StringUtil.isNullOrEmpty(token) && TokenUtil.validateAdmin(token)){ 38 | request.setAttribute("openid",TokenUtil.getClaimsFromToken(token).get("openid")); 39 | return true; 40 | } 41 | 42 | // token无效 43 | Result result = new Result(-1,"此接口为管理员接口",null); 44 | String strResponseJson = JSON.toJSONString(result); 45 | response.setContentType("application/json;charset=UTF-8"); 46 | try (OutputStream out = response.getOutputStream()) { 47 | out.write(strResponseJson.getBytes(StandardCharsets.UTF_8)); 48 | out.flush(); 49 | } 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/IPUtil.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | 4 | import cn.hutool.Hutool; 5 | import cn.hutool.core.net.Ipv4Util; 6 | import cn.hutool.core.net.NetUtil; 7 | import cn.hutool.core.util.StrUtil; 8 | import cn.hutool.http.useragent.UserAgentUtil; 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.util.StringUtils; 12 | 13 | import java.net.InetAddress; 14 | import java.net.UnknownHostException; 15 | import java.util.Enumeration; 16 | 17 | @Slf4j 18 | public class IPUtil { 19 | private static final String IP_UTILS_FLAG = ","; 20 | private static final String UNKNOWN = "unknown"; 21 | private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1"; 22 | private static final String LOCALHOST_IP1 = "127.0.0.1"; 23 | 24 | public static String getIpAddr(HttpServletRequest request) { 25 | String ip = null; 26 | try { 27 | ip = request.getHeader("x-forwarded-for"); 28 | if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { 29 | ip = request.getHeader("Proxy-Client-IP"); 30 | } 31 | if (StrUtil.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 32 | ip = request.getHeader("WL-Proxy-Client-IP"); 33 | } 34 | if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { 35 | ip = request.getHeader("HTTP_CLIENT_IP"); 36 | } 37 | if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { 38 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 39 | } 40 | if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { 41 | ip = request.getRemoteAddr(); 42 | } 43 | } catch (Exception e) { 44 | log.error("IPUtils ERROR:", e); 45 | } 46 | 47 | //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 48 | if (!StrUtil.isEmpty(ip) && ip.length() > 15) { 49 | if (ip.indexOf(",") > 0) { 50 | ip = ip.substring(0, ip.indexOf(",")); 51 | } 52 | } 53 | 54 | return ip; 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/interceptor/AuthInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.interceptor; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.uuorb.journal.annotation.Authorization; 5 | import com.uuorb.journal.constant.ResultStatus; 6 | import com.uuorb.journal.controller.vo.Result; 7 | import com.uuorb.journal.util.TokenUtil; 8 | import io.netty.util.internal.StringUtil; 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import jakarta.servlet.http.HttpServletResponse; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.web.method.HandlerMethod; 14 | import org.springframework.web.servlet.HandlerInterceptor; 15 | 16 | import java.io.OutputStream; 17 | import java.nio.charset.StandardCharsets; 18 | 19 | @Slf4j 20 | @Service 21 | public class AuthInterceptor implements HandlerInterceptor { 22 | @Override 23 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 24 | Object handler) throws Exception { 25 | if (handler instanceof HandlerMethod) { 26 | HandlerMethod hm = (HandlerMethod) handler; 27 | Authorization signature = hm.getMethodAnnotation(Authorization.class); 28 | if (signature == null) { 29 | return true; 30 | } 31 | 32 | // 验证签名的方法 33 | String token = request.getHeader("Authorization"); 34 | // token存在且有效 35 | if (!StringUtil.isNullOrEmpty(token) && TokenUtil.validateToken(token)) { 36 | request.setAttribute("openid", TokenUtil.getUserOpenid(token)); 37 | return true; 38 | } 39 | 40 | // token无效 41 | Result result = Result.error(ResultStatus.TOKEN_VALID); 42 | String strResponseJson = JSON.toJSONString(result); 43 | response.setContentType("application/json;charset=UTF-8"); 44 | try (OutputStream out = response.getOutputStream()) { 45 | out.write(strResponseJson.getBytes(StandardCharsets.UTF_8)); 46 | out.flush(); 47 | } 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/ExpenseService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.uuorb.journal.constant.ResultStatus; 6 | import com.uuorb.journal.exception.CustomException; 7 | import com.uuorb.journal.mapper.ExpenseMapper; 8 | import com.uuorb.journal.model.Expense; 9 | import com.uuorb.journal.util.IDUtil; 10 | import jakarta.annotation.Resource; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.List; 14 | 15 | @Service 16 | public class ExpenseService { 17 | 18 | @Resource 19 | ActivityService activityService; 20 | 21 | @Resource 22 | ExpenseMapper expenseMapper; 23 | 24 | public PageInfo queryList(String activityId, Integer pageNum) { 25 | PageHelper.startPage(pageNum, 20); 26 | List expenseList = expenseMapper.queryList(activityId); 27 | return new PageInfo<>(expenseList); 28 | } 29 | 30 | public List queryListUnlimited(String activityId) { 31 | return expenseMapper.queryList(activityId); 32 | } 33 | 34 | public Expense queryById(String expenseId) { 35 | return expenseMapper.queryById(expenseId); 36 | } 37 | 38 | public void insertExpenseAndCalcRemainingBudget(Expense expense) { 39 | expense.setExpenseId(IDUtil.expenseId()); 40 | // 入库 41 | expenseMapper.insert(expense); 42 | // 计算剩余预算 43 | activityService.refreshActivityRemainingBudget(expense.getActivityId()); 44 | } 45 | 46 | public void update(Expense expense) { 47 | expenseMapper.update(expense); 48 | //todo: 需要重新算一遍activity的余额 49 | activityService.refreshActivityRemainingBudget(expense.getActivityId()); 50 | } 51 | 52 | public void checkIsOwner(Expense expense) throws CustomException { 53 | Integer count = expenseMapper.count(expense); 54 | if (count <= 0) { 55 | throw new CustomException(ResultStatus.RESOURCE_NOT_FOUND); 56 | } 57 | } 58 | 59 | public void deleteExpense(String expenseId, String activityId) { 60 | expenseMapper.delete(expenseId); 61 | activityService.refreshActivityRemainingBudget(activityId); 62 | 63 | } 64 | 65 | // todo: 删除 66 | } 67 | -------------------------------------------------------------------------------- /src/main/resources/mapper/ExpenseMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UPDATE expense 6 | 7 | 8 | 9 | expense_time = #{expenseTime}, 10 | 11 | 12 | 13 | positive = #{positive}, 14 | 15 | 16 | type = #{type}, 17 | 18 | 19 | price = #{price}, 20 | 21 | 22 | label = #{label}, 23 | 24 | 25 | where expense_id = #{expenseId} AND activity_id = #{activityId} 26 | 27 | 28 | 37 | 38 | 47 | 48 | 54 | 55 | 56 | INSERT INTO expense(expense_id, type, price, label, user_id, activity_id,positive,expense_time) 57 | VALUES (#{expenseId}, #{type}, #{price}, #{label}, #{userId}, #{activityId},#{positive},#{expenseTime}) 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/SMSUtil.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | import com.tencentcloudapi.common.Credential; 4 | import com.tencentcloudapi.common.exception.TencentCloudSDKException; 5 | import com.tencentcloudapi.common.profile.ClientProfile; 6 | import com.tencentcloudapi.common.profile.HttpProfile; 7 | import com.tencentcloudapi.sms.v20210111.SmsClient; 8 | import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.springframework.beans.factory.annotation.Value; 12 | 13 | @Slf4j 14 | // 腾讯云发送短信 15 | public class SMSUtil { 16 | 17 | @Value("${tencent.sms.secretId}") 18 | private String SECRET_ID; 19 | 20 | @Value("${tencent.sms.secretKey}") 21 | private String SECRET_KEY; 22 | 23 | public void sendLoginMsg(String phone, String code) { 24 | try { 25 | Credential cred = new Credential(SECRET_ID, SECRET_KEY); 26 | HttpProfile httpProfile = new HttpProfile(); 27 | httpProfile.setReqMethod("POST"); 28 | httpProfile.setConnTimeout(60); 29 | httpProfile.setEndpoint("sms.tencentcloudapi.com"); 30 | ClientProfile clientProfile = new ClientProfile(); 31 | clientProfile.setSignMethod("HmacSHA256"); 32 | clientProfile.setHttpProfile(httpProfile); 33 | SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile); 34 | SendSmsRequest req = getSendSmsRequest(phone, code); 35 | 36 | client.SendSms(req); 37 | 38 | } catch (TencentCloudSDKException e) { 39 | log.error("发送短信失败:{},phone:{}", e.getMessage(), phone); 40 | } 41 | } 42 | 43 | private static @NotNull SendSmsRequest getSendSmsRequest(String phone, String code) { 44 | SendSmsRequest req = new SendSmsRequest(); 45 | String sdkAppId = "1400773974"; 46 | req.setSmsSdkAppId(sdkAppId); 47 | String signName = "轻效科技"; 48 | req.setSignName(signName); 49 | String templateId = "1625672"; 50 | req.setTemplateId(templateId); 51 | 52 | String[] templateParamSet = {code}; 53 | req.setTemplateParamSet(templateParamSet); 54 | 55 | String[] phoneNumberSet = {"+86" + phone}; 56 | req.setPhoneNumberSet(phoneNumberSet); 57 | 58 | String sessionContext = ""; 59 | req.setSessionContext(sessionContext); 60 | 61 | String extendCode = ""; 62 | req.setExtendCode(extendCode); 63 | 64 | String senderid = ""; 65 | req.setSenderId(senderid); 66 | return req; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/mapper/ChartsMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 51 | 52 | 53 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/aop/LogAspect.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.aop; 2 | 3 | import cn.hutool.core.date.StopWatch; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONException; 6 | import com.uuorb.journal.mapper.LogMapper; 7 | import com.uuorb.journal.model.LogBean; 8 | import com.uuorb.journal.util.IPUtil; 9 | import com.uuorb.journal.util.TokenUtil; 10 | import jakarta.annotation.Resource; 11 | import jakarta.servlet.http.HttpServletRequest; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.aspectj.lang.ProceedingJoinPoint; 14 | import org.aspectj.lang.annotation.Around; 15 | import org.aspectj.lang.annotation.Aspect; 16 | import org.aspectj.lang.annotation.Pointcut; 17 | import org.springframework.stereotype.Component; 18 | import org.springframework.web.context.request.RequestContextHolder; 19 | import org.springframework.web.context.request.ServletRequestAttributes; 20 | 21 | import java.util.Date; 22 | import java.util.Objects; 23 | 24 | @Aspect 25 | @Component 26 | @Slf4j 27 | public class LogAspect { 28 | 29 | @Resource 30 | LogMapper logMapper; 31 | 32 | // 令牌自定义标识 33 | private String header = "Authorization"; 34 | 35 | // 定义一个切入点 36 | @Pointcut("@annotation(com.uuorb.journal.annotation.Log)") 37 | public void log() { 38 | 39 | } 40 | 41 | @Around("log()") 42 | public Object log(ProceedingJoinPoint pjp) throws Throwable { 43 | try { 44 | //得到request对象 45 | HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); 46 | String requestURI = request.getRequestURI(); 47 | //获取请求参数 48 | Object[] args = pjp.getArgs(); 49 | String argStr = ""; 50 | try { 51 | argStr = JSON.toJSONString(args); 52 | } catch (JSONException e) { 53 | argStr = "参数转换异常"; 54 | } 55 | 56 | String userID = ""; 57 | if (request.getHeader(header) != null) { 58 | String token = request.getHeader(header); 59 | userID = TokenUtil.getUserId(token); 60 | } 61 | 62 | String ipAddr = IPUtil.getIpAddr(request); 63 | StopWatch stopWatch = new StopWatch(); 64 | stopWatch.start(); 65 | LogBean logBean = LogBean.builder() 66 | .url(requestURI) 67 | .createTime(new Date()) 68 | .functionName(pjp.getSignature().getName()) 69 | .params(argStr) 70 | .httpMethod(request.getMethod()) 71 | .userID(userID) 72 | .ip(ipAddr) 73 | .build(); 74 | Object proceed = pjp.proceed(); 75 | 76 | stopWatch.stop(); 77 | long totalTimeMillis = stopWatch.getTotalTimeMillis(); 78 | logBean.setDuration(totalTimeMillis); 79 | logMapper.insertLog(logBean); 80 | return proceed; 81 | } catch (Throwable throwable) { 82 | Object proceed = pjp.proceed(); 83 | return proceed; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | SELECT users.id as UID, 28 | users.ai_avatar_url as aiAvatarUrl, 29 | users.user_id as userId, 30 | users.nickname, 31 | users.avatar_url as avatarUrl, 32 | users.openid, 33 | users.union_id as unionId, 34 | users.telephone as telephone, 35 | users.vip, 36 | users.vip_expire_time as vipExpireTime, 37 | users.last_login_time as lastLoginTime, 38 | users.current_activity_id as currentActivityId, 39 | users.opening_statement as openingStatement, 40 | users.salutation, 41 | users.relationship, 42 | users.personality, 43 | users.create_time as createTime, 44 | users.update_time as updateTime 45 | 46 | 47 | INSERT INTO users(user_id, nickname, telephone,openid,union_id,apple_id) 48 | VALUES (#{userId}, #{nickname}, #{telephone},#{openid},#{unionId},#{appleId}) 49 | 50 | 51 | 61 | 62 | 63 | UPDATE users 64 | 65 | 66 | avatar_url = #{avatarUrl}, 67 | 68 | 69 | ai_avatar_url = #{aiAvatarUrl}, 70 | 71 | 72 | 73 | 74 | nickname = #{nickname}, 75 | 76 | 77 | current_activity_id = #{currentActivityId}, 78 | 79 | 80 | opening_statement = #{openingStatement}, 81 | 82 | 83 | salutation = #{salutation}, 84 | 85 | 86 | relationship = #{relationship}, 87 | 88 | 89 | personality = #{personality}, 90 | 91 | 92 | where user_id = #{userId} 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/ActivityService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import com.uuorb.journal.mapper.ActivityMapper; 4 | import com.uuorb.journal.mapper.UserMapper; 5 | import com.uuorb.journal.model.Activity; 6 | import com.uuorb.journal.model.ActivityUserRel; 7 | import com.uuorb.journal.model.User; 8 | import com.uuorb.journal.util.IDUtil; 9 | import jakarta.annotation.Resource; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.List; 14 | 15 | @Service 16 | public class ActivityService { 17 | 18 | @Resource 19 | ActivityMapper mapper; 20 | @Autowired 21 | private UserMapper userMapper; 22 | @Autowired 23 | private UserService userService; 24 | 25 | public Activity insert(Activity activity) { 26 | String activityId = IDUtil.activityId(); 27 | String userId = activity.getUserId(); 28 | activity.setActivityId(activityId); 29 | 30 | // 判断默认 31 | if (activity.getActivated()) { 32 | mapper.updateAllInActive(activityId, userId); 33 | // 更新user中的activityId 34 | User user = User.builder().userId(userId).currentActivityId(activityId).build(); 35 | userMapper.updateUser(user); 36 | } 37 | 38 | activity.setRemainingBudget(activity.getBudget()); 39 | // 插入一条引用 40 | ActivityUserRel joinQuery = ActivityUserRel.builder().userId(userId).activityId(activityId).build(); 41 | mapper.joinActivity(joinQuery); 42 | mapper.insert(activity); 43 | return activity; 44 | } 45 | 46 | public boolean isOwnerActivity(Activity activity) { 47 | return mapper.isOwner(activity); 48 | } 49 | 50 | public boolean isJoinedActivity(Activity activity) { 51 | return mapper.isJoinedOwner(activity); 52 | } 53 | 54 | public boolean hasQueryPermission(Activity activity) { 55 | return isOwnerActivity(activity) || isJoinedActivity(activity); 56 | } 57 | 58 | public List querySelfActivityList(Activity activity) { 59 | List activityList = mapper.querySelfActivityList(activity); 60 | 61 | for (Activity activityItem : activityList) { 62 | String activityId = activityItem.getActivityId(); 63 | 64 | 65 | } 66 | 67 | return activityList; 68 | } 69 | 70 | public Activity queryActivityByActivityId(String activityId) { 71 | return mapper.queryActivityByActivityId(activityId); 72 | } 73 | 74 | public List queryJoinedActivityList(Activity activity) { 75 | return mapper.queryJoinedActivityList(activity); 76 | } 77 | 78 | public Integer update(Activity activity) { 79 | String activityId = activity.getActivityId(); 80 | String userId = activity.getUserId(); 81 | // 如果设置为默认 82 | if (activity.getActivated()) { 83 | mapper.updateAllInActive(activityId, userId); 84 | // 更新user中的activityId 85 | User user = User.builder().userId(userId).currentActivityId(activityId).build(); 86 | userMapper.updateUser(user); 87 | } 88 | 89 | // 如果取消设置默认,则需要看一下user表里的currentActivityId,是不是当前的 90 | if (!activity.getActivated()) { 91 | User user = userService.getUserByUserId(userId); 92 | if (activityId.equalsIgnoreCase(user.getCurrentActivityId())) { 93 | userMapper.setCurrentActivityNull(userId); 94 | } 95 | } 96 | Integer i = mapper.updateActivity(activity); 97 | // 需要重新算一遍activity的余额 98 | refreshActivityRemainingBudget(activityId); 99 | return i; 100 | } 101 | 102 | public void joinActivity(ActivityUserRel joinQuery) { 103 | mapper.joinActivity(joinQuery); 104 | } 105 | 106 | public void refreshActivityRemainingBudget(String activityId) { 107 | mapper.refreshActivityRemainingBudget(activityId); 108 | } 109 | 110 | public Activity getCurrentActivity(String userId) { 111 | return mapper.getCurrentActivity(userId); 112 | } 113 | 114 | /** 115 | * 保留expense,用于恢复 116 | * @param userId 117 | * @param activityId 118 | * @return 119 | */ 120 | public boolean deleteActivity(String userId, String activityId) { 121 | // 删除所有引用 122 | mapper.deleteAllRef(activityId); 123 | // 删除账本 124 | mapper.deleteActivity(activityId); 125 | // 删除所有用户当前的账本 126 | mapper.deleteAllUserActivityRef(activityId); 127 | return true; 128 | } 129 | 130 | public Activity searchActivity(String activityId) { 131 | return mapper.queryActivityByActivityId(activityId); 132 | } 133 | 134 | public void exitActivity(String activityId, String userId) { 135 | mapper.exitActivity(activityId,userId); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/ExpenseController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import com.github.pagehelper.PageInfo; 4 | import com.uuorb.journal.annotation.Authorization; 5 | import com.uuorb.journal.annotation.Log; 6 | import com.uuorb.journal.annotation.UserId; 7 | import com.uuorb.journal.constant.ResultStatus; 8 | import com.uuorb.journal.controller.vo.Result; 9 | import com.uuorb.journal.model.Activity; 10 | import com.uuorb.journal.model.Expense; 11 | import com.uuorb.journal.model.User; 12 | import com.uuorb.journal.service.ActivityService; 13 | import com.uuorb.journal.service.ExpenseService; 14 | import com.uuorb.journal.service.UserService; 15 | import jakarta.annotation.Resource; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | @RestController 19 | @RequestMapping("/expense") 20 | public class ExpenseController { 21 | 22 | @Resource 23 | UserService userService; 24 | 25 | @Resource 26 | ExpenseService expenseService; 27 | 28 | @Resource 29 | ActivityService activityService; 30 | 31 | @Log 32 | 33 | @DeleteMapping("{expenseId}/{activityId}") 34 | Result deleteExpense(@PathVariable("expenseId") String expenseId, @PathVariable("activityId") String activityId) { 35 | // todo: 校验 36 | expenseService.deleteExpense(expenseId, activityId); 37 | 38 | return Result.ok(); 39 | } 40 | 41 | 42 | /** 43 | * 获取自己的账单列表 44 | * @param activityId: 账本id 45 | * @param pageNum: 分页页码 46 | * @param userId: 用户ID,从header中取jwt解析 47 | * @return 账单列表 48 | */ 49 | @Log 50 | @GetMapping("/list/{activityId}") 51 | Result> getActivityExpenseList( 52 | @PathVariable("activityId") String activityId, 53 | @RequestParam(value = "pageNum", required = false) Integer pageNum, 54 | @UserId String userId) { 55 | 56 | Activity query = Activity.builder().activityId(activityId).userId(userId).build(); 57 | 58 | // 判断有没有查询权限 59 | boolean hasPermission = activityService.hasQueryPermission(query); 60 | if (!hasPermission) { 61 | return Result.error(ResultStatus.NOT_OWN_RESOURCE); 62 | } 63 | 64 | if (pageNum == null) { 65 | pageNum = 1; 66 | } 67 | 68 | // 查询 69 | PageInfo pageInfo = expenseService.queryList(activityId, pageNum); 70 | 71 | return Result.ok(pageInfo); 72 | } 73 | 74 | @Authorization 75 | @Log 76 | @PostMapping 77 | Result insert(@RequestBody Expense expense, @UserId String userId) { 78 | expense.setUserId(userId); 79 | expenseService.insertExpenseAndCalcRemainingBudget(expense); 80 | return Result.ok(expense); 81 | } 82 | 83 | @Authorization 84 | @Log 85 | @PostMapping("/current") 86 | Result insertCurrent(@RequestBody Expense expense, @UserId String userId) { 87 | expense.setUserId(userId); 88 | 89 | User user = userService.getUserByUserId(userId); 90 | 91 | String currentActivityId = user.getCurrentActivityId(); 92 | 93 | if (currentActivityId == null) { 94 | return Result.error(ResultStatus.NOT_OWN_RESOURCE); 95 | } else { 96 | expense.setActivityId(user.getCurrentActivityId()); 97 | expenseService.insertExpenseAndCalcRemainingBudget(expense); 98 | } 99 | 100 | return Result.ok(expense); 101 | } 102 | 103 | @Log 104 | @PatchMapping 105 | Result update(@RequestBody Expense expense, @UserId String userId) { 106 | String expenseId = expense.getExpenseId(); 107 | String activityId = expense.getActivityId(); 108 | // 1. 判断是否有expenseId和activityId 109 | if (expenseId == null || expense.getActivityId() == null) { 110 | return Result.error(ResultStatus.PRIMARY_ID_MISS); 111 | } 112 | 113 | // 2. 判断活动是否存在 114 | Activity activity = activityService.queryActivityByActivityId(activityId); 115 | if (activity == null) { 116 | return Result.error(ResultStatus.RESOURCE_NOT_FOUND); 117 | } 118 | 119 | // 3. 判断有没有查询权限 120 | expense.setUserId(userId); 121 | 122 | Activity query = Activity.builder().activityId(expense.getActivityId()).userId(userId).build(); 123 | boolean hasPermission = activityService.hasQueryPermission(query); 124 | if (!hasPermission) { 125 | return Result.error(ResultStatus.NOT_OWN_RESOURCE); 126 | } 127 | // 4. 判断expense是否是自己的 128 | // todo: 这个应该是配置项,先放开了 129 | // try { 130 | // expenseService.checkIsOwner(expense); 131 | // } catch (CustomException e) { 132 | // return Result.error(e.getCustomErrEnum()); 133 | // } 134 | 135 | expenseService.update(expense); 136 | 137 | return Result.ok(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.Validator; 5 | import cn.hutool.core.util.RandomUtil; 6 | import com.uuorb.journal.annotation.Authorization; 7 | import com.uuorb.journal.annotation.Log; 8 | import com.uuorb.journal.annotation.UserId; 9 | import com.uuorb.journal.constant.CacheConstant; 10 | import com.uuorb.journal.constant.ResultStatus; 11 | import com.uuorb.journal.controller.vo.Result; 12 | import com.uuorb.journal.model.User; 13 | import com.uuorb.journal.service.UserService; 14 | import com.uuorb.journal.util.IPUtil; 15 | import com.uuorb.journal.util.RedisUtil; 16 | import com.uuorb.journal.util.SMSUtil; 17 | import com.uuorb.journal.util.TokenUtil; 18 | import jakarta.annotation.Resource; 19 | import jakarta.servlet.http.HttpServletRequest; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.web.bind.annotation.*; 23 | 24 | import java.security.NoSuchAlgorithmException; 25 | import java.security.spec.InvalidKeySpecException; 26 | import java.util.List; 27 | 28 | @Slf4j 29 | @RestController 30 | @RequestMapping("/user") 31 | public class UserController { 32 | 33 | @Resource 34 | RedisUtil redisUtil; 35 | 36 | @Resource 37 | private UserService userService; 38 | 39 | @Autowired 40 | SMSUtil smsUtil; 41 | 42 | @Log 43 | @Authorization 44 | @GetMapping("/profile/me") 45 | Result getSelfProfile(@UserId String userId) { 46 | return Result.ok(userService.getUserByUserId(userId)); 47 | } 48 | 49 | @PostMapping("/login/smsCode") 50 | Result sendSmsCode(@RequestParam("telephone") String telephone, HttpServletRequest request) { 51 | log.info("发送手机验证码:{}", telephone); 52 | boolean isMobile = Validator.isMobile(telephone); 53 | if (!isMobile) { 54 | return Result.error(ResultStatus.TELEPHONE_ERROR); 55 | } 56 | 57 | // 通过request获取ip 58 | String ipAddr = IPUtil.getIpAddr(request); 59 | String key = CacheConstant.SEND_SMS_IP + ipAddr; 60 | Long count = redisUtil.incr(key, 1); 61 | 62 | if (count.equals(1L)) { 63 | redisUtil.expire(key, 60); 64 | } 65 | 66 | if (count > 1) { 67 | return Result.error(ResultStatus.VERIFY_CODE_LIMITED); 68 | } 69 | 70 | // 生成4位数字 71 | String code = RandomUtil.randomNumbers(4); 72 | 73 | // 5分钟有效 74 | redisUtil.set(CacheConstant.LOGIN_CODE + telephone, code, 5 * 60); 75 | smsUtil.sendLoginMsg(telephone, code); 76 | 77 | log.info("登陆验证码:{},{}", telephone, ipAddr); 78 | return Result.ok(); 79 | } 80 | 81 | @PostMapping("/login") 82 | Result login(@RequestParam("telephone") String telephone, @RequestParam(name = "code") String code) { 83 | boolean isMobile = Validator.isMobile(telephone); 84 | 85 | if (!isMobile) { 86 | return Result.error(ResultStatus.TELEPHONE_ERROR); 87 | } 88 | String userId; 89 | 90 | // 校验 code 91 | Object o = redisUtil.get(CacheConstant.LOGIN_CODE + telephone); 92 | if (o == null || !o.toString().equalsIgnoreCase(code)) { 93 | return Result.error(ResultStatus.VERIFY_CODE_ERROR); 94 | } 95 | 96 | // 删除code 97 | redisUtil.del(CacheConstant.LOGIN_CODE + telephone); 98 | 99 | // 先查,如果存在,则返回token,否则注册 100 | List userList = userService.selectUserByPhone(telephone); 101 | if (CollectionUtil.isNotEmpty(userList)) { 102 | userId = userList.get(0).getUserId(); 103 | } else { 104 | User user = userService.registerByPhone(telephone); 105 | userId = user.getUserId(); 106 | } 107 | 108 | String token = TokenUtil.generateToken(userId); 109 | return Result.ok(token); 110 | } 111 | 112 | @PostMapping("/login/wechat") 113 | Result loginWechat(@RequestParam("code") String code, @RequestParam("platform") String platform) { 114 | String token = userService.loginWithWechat(code, platform); 115 | return Result.ok(token); 116 | } 117 | 118 | @PostMapping("/login/apple") 119 | Result loginWithApple(@RequestParam("code") String code) { 120 | String token = null; 121 | try { 122 | token = userService.loginWithApple(code); 123 | return Result.ok(token); 124 | 125 | } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 126 | return Result.error(); 127 | } 128 | } 129 | 130 | private Result forMe(String telephone) { 131 | List userList = userService.selectUserByPhone(telephone); 132 | String userId = userList.get(0).getUserId(); 133 | String token = TokenUtil.generateToken(userId); 134 | return Result.ok(token); 135 | } 136 | 137 | @Log 138 | @Authorization 139 | @PatchMapping 140 | Result updateUser(@UserId String userId, @RequestBody User user) { 141 | user.setUserId(userId); 142 | userService.updateUser(user); 143 | return Result.ok(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/KimiService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.date.LocalDateTimeUtil; 5 | import com.alibaba.fastjson2.JSONObject; 6 | import com.uuorb.journal.model.AIConfig; 7 | import com.uuorb.journal.model.EngelExpense; 8 | import com.uuorb.journal.model.Expense; 9 | import com.uuorb.journal.model.kimi.KimiMessage; 10 | import com.uuorb.journal.util.KimiUtils; 11 | import com.uuorb.journal.model.kimi.RoleEnum; 12 | import jakarta.annotation.Resource; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.io.OutputStream; 16 | import java.time.LocalDateTime; 17 | import java.util.List; 18 | 19 | @Service("kimi") 20 | public class KimiService implements AiService { 21 | 22 | @Resource 23 | KimiUtils kimiUtils; 24 | 25 | final static String FORMAT_PROMPT = "你是记账格式化机器人,请使用如下 JSON 格式输出你的回复: {type:string,price:double,label:string,positive:number,expenseTime:string}。positive0为支出,1为收入,expenseTime:yyyy-mm-dd hh:dd:ss。例:昨天从烟台打车到蓬莱176,返回{type:'交通',price:176.00,label:'打车从烟台到蓬莱',positive:0},label保持原有意思不变,可以使得更容易理解,label尽量从列表中选择:美食,服装,捐赠,娱乐,燃料,房租,投资,宠物,化妆品,药品,电话费,购物,烟酒,学习,旅游,交通,其他,工资,红包,转账'}"; 26 | 27 | final static String PRAISE_DEFAULT_PROMPT = "你是个夸夸机器人,你的角色是女儿,称呼我为爸爸,你的性格是活泼开朗。我会跟你说我的花销,你给我回应。字数在10-30左右"; 28 | 29 | final static String PRAISE_PROMPT = "你是个夸夸机器人,你的角色是${relationship},称呼我为${salutation},你的性格是${personality}。我会跟你说我的花销,你给我回应。字数在10-30左右"; 30 | 31 | final static String GREETING_PROMPT = "你的角色是${relationship},称呼我为${salutation},你的性格是${personality}。我们上次见面是${lastLoginTime},不一定要强调上次见面时间,视情况而定,现在我走到你面前了,你给我打个招呼吧。字数在10-20左右"; 32 | 33 | final static String ENGEL_PROMPT = "帮我计算恩格尔系数;"; 34 | 35 | @Override 36 | public Expense formatExpense(String naturalSentence) { 37 | // 当前的时间 38 | String now = LocalDateTimeUtil.formatNormal(LocalDateTime.now()); 39 | // 昨天话费了10块 -> expenseTime: 2025-05-11 40 | List messages = CollUtil.newArrayList( 41 | new KimiMessage(RoleEnum.system.name(), 42 | FORMAT_PROMPT + "现在时间是: " + now), 43 | new KimiMessage(RoleEnum.user.name(), naturalSentence)); 44 | 45 | KimiMessage chat = kimiUtils.chat(KIMI_MODEL, messages); 46 | try { 47 | return JSONObject.parseObject(chat.getContent(), Expense.class); 48 | } catch (Exception e) { 49 | return null; 50 | } 51 | } 52 | 53 | @Override 54 | public String praise(String sentence) { 55 | List messages = CollUtil.newArrayList(new KimiMessage(RoleEnum.system.name(), 56 | PRAISE_DEFAULT_PROMPT), new KimiMessage(RoleEnum.user.name(), sentence)); 57 | // todo: 记录用户调用的请求和响应 58 | KimiMessage chat = kimiUtils.chat(KIMI_MODEL, messages); 59 | return chat.getContent(); 60 | } 61 | 62 | public String praise(String sentence, AIConfig config) { 63 | var prompt = PRAISE_PROMPT.replace("${relationship}", config.getRelationship()) 64 | .replace("${personality}", config.getPersonality()) 65 | .replace("${salutation}", config.getSalutation()); 66 | List messages = CollUtil.newArrayList(new KimiMessage(RoleEnum.system.name(), prompt), 67 | new KimiMessage(RoleEnum.user.name(), sentence)); 68 | // todo: 记录用户调用的请求和响应 69 | KimiMessage chat = kimiUtils.chat(KIMI_MODEL, messages); 70 | return chat.getContent(); 71 | } 72 | 73 | public String greet(AIConfig config) { 74 | String prompt = GREETING_PROMPT.replace("${relationship}", config.getRelationship()) 75 | .replace("${personality}", config.getPersonality()) 76 | .replace("${salutation}", config.getSalutation()) 77 | .replace("${lastLoginTime}", config.getLastLoginTime()); 78 | 79 | List messages = CollUtil.newArrayList(new KimiMessage(RoleEnum.system.name(), prompt)); 80 | // todo: 记录用户调用的请求和响应 81 | KimiMessage chat = kimiUtils.chat(KIMI_MODEL, messages); 82 | return chat.getContent(); 83 | } 84 | 85 | @Override 86 | public String generateImage(String model, String description, String role) { 87 | return "not implement"; 88 | } 89 | 90 | @Override 91 | public void praiseStream(String sentence, AIConfig config, OutputStream outputStream) { 92 | var prompt = PRAISE_PROMPT.replace("${relationship}", config.getRelationship()) 93 | .replace("${personality}", config.getPersonality()) 94 | .replace("${salutation}", config.getSalutation()); 95 | List messages = CollUtil.newArrayList(new KimiMessage(RoleEnum.system.name(), prompt), 96 | new KimiMessage(RoleEnum.user.name(), sentence)); 97 | // todo: 记录用户调用的请求和响应 98 | kimiUtils.chatInStream(KIMI_MODEL, messages, outputStream); 99 | } 100 | 101 | @Override 102 | public String tts(String sentence, AIConfig aiConfig) { 103 | return ""; 104 | } 105 | 106 | @Override 107 | public void engelExplain(List expenses, OutputStream outputStream) { 108 | List messages = CollUtil.newArrayList(new KimiMessage(RoleEnum.system.name(), ENGEL_PROMPT), 109 | new KimiMessage(RoleEnum.user.name(), expenses.toString())); 110 | kimiUtils.chatInStream(KIMI_MODEL, messages, outputStream); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.config; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; 8 | import org.springframework.cache.CacheManager; 9 | import org.springframework.cache.annotation.CachingConfigurerSupport; 10 | import org.springframework.cache.annotation.EnableCaching; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 14 | import org.springframework.data.redis.cache.RedisCacheManager; 15 | import org.springframework.data.redis.connection.RedisConnectionFactory; 16 | import org.springframework.data.redis.core.*; 17 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 18 | import org.springframework.data.redis.serializer.RedisSerializationContext; 19 | import org.springframework.data.redis.serializer.RedisSerializer; 20 | import org.springframework.data.redis.serializer.StringRedisSerializer; 21 | 22 | import java.time.Duration; 23 | 24 | @Configuration 25 | @EnableCaching 26 | public class RedisConfig extends CachingConfigurerSupport { 27 | @Bean 28 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 29 | 30 | RedisTemplate redisTemplate = new RedisTemplate<>(); 31 | // 配置连接工厂 32 | redisTemplate.setConnectionFactory(redisConnectionFactory); 33 | 34 | // 使用Jackson2JsonRedisSerialize 替换默认序列化 35 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 36 | 37 | ObjectMapper objectMapper = new ObjectMapper(); 38 | // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public 39 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 40 | // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 41 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 42 | 43 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 44 | 45 | // 设置value的序列化规则和 key的序列化规则 46 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 47 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 48 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 49 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 50 | redisTemplate.afterPropertiesSet(); 51 | 52 | return redisTemplate; 53 | } 54 | 55 | @Bean 56 | public CacheManager cacheManager(RedisConnectionFactory factory) { 57 | RedisSerializer redisSerializer = new StringRedisSerializer(); 58 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 59 | 60 | //解决查询缓存转换异常的问题 61 | ObjectMapper om = new ObjectMapper(); 62 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 63 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 64 | jackson2JsonRedisSerializer.setObjectMapper(om); 65 | 66 | // 配置序列化(解决乱码的问题),过期时间40小时 67 | RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() 68 | .entryTtl(Duration.ofDays(10)) 69 | .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) 70 | .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) 71 | .disableCachingNullValues(); 72 | 73 | RedisCacheManager cacheManager = RedisCacheManager.builder(factory) 74 | .cacheDefaults(config) 75 | .build(); 76 | return cacheManager; 77 | } 78 | 79 | /** 80 | * 对hash类型的数据操作 81 | * 82 | * @param redisTemplate 83 | * @return 84 | */ 85 | @Bean 86 | public HashOperations hashOperations(RedisTemplate redisTemplate) { 87 | return redisTemplate.opsForHash(); 88 | } 89 | 90 | /** 91 | * 对redis字符串类型数据操作 92 | * 93 | * @param redisTemplate 94 | * @return 95 | */ 96 | @Bean 97 | public ValueOperations valueOperations(RedisTemplate redisTemplate) { 98 | return redisTemplate.opsForValue(); 99 | } 100 | 101 | /** 102 | * 对链表类型的数据操作 103 | * 104 | * @param redisTemplate 105 | * @return 106 | */ 107 | @Bean 108 | public ListOperations listOperations(RedisTemplate redisTemplate) { 109 | return redisTemplate.opsForList(); 110 | } 111 | 112 | /** 113 | * 对无序集合类型的数据操作 114 | * 115 | * @param redisTemplate 116 | * @return 117 | */ 118 | @Bean 119 | public SetOperations setOperations(RedisTemplate redisTemplate) { 120 | return redisTemplate.opsForSet(); 121 | } 122 | 123 | /** 124 | * 对有序集合类型的数据操作 125 | * 126 | * @param redisTemplate 127 | * @return 128 | */ 129 | @Bean 130 | public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { 131 | return redisTemplate.opsForZSet(); 132 | } 133 | } 134 | 135 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/ActivityController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import com.uuorb.journal.annotation.Authorization; 5 | import com.uuorb.journal.annotation.UserId; 6 | import com.uuorb.journal.constant.ResultStatus; 7 | import com.uuorb.journal.controller.vo.Result; 8 | import com.uuorb.journal.model.Activity; 9 | import com.uuorb.journal.model.ActivityUserRel; 10 | import com.uuorb.journal.model.User; 11 | import com.uuorb.journal.service.ActivityService; 12 | import jakarta.annotation.Resource; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import java.util.List; 17 | 18 | @Slf4j 19 | @RestController 20 | @RequestMapping("activity") 21 | public class ActivityController { 22 | 23 | @Resource 24 | ActivityService service; 25 | 26 | 27 | /** 28 | * 删除活动 29 | * 30 | * @param userId 31 | * @return 32 | */ 33 | @Authorization 34 | @DeleteMapping("/{activityId}") 35 | Result deleteActivity(@UserId String userId, @PathVariable("activityId") String activityId) { 36 | 37 | Activity activityQuery = Activity.builder().activityId(activityId).userId(userId).build(); 38 | boolean isOwnerActivity = service.isOwnerActivity(activityQuery); 39 | if (!isOwnerActivity) { 40 | return Result.error(ResultStatus.NOT_OWN_RESOURCE); 41 | } 42 | 43 | 44 | return Result.ok(service.deleteActivity(userId, activityId)); 45 | } 46 | 47 | /** 48 | * 创建的活动 49 | */ 50 | @Authorization 51 | @GetMapping("/list") 52 | Result getActivityList(@UserId String userId) { 53 | Activity query = Activity.builder().userId(userId).build(); 54 | return Result.ok(service.querySelfActivityList(query)); 55 | } 56 | 57 | 58 | @Authorization 59 | @GetMapping("/search/{activityId}") 60 | Result searchActivity(@PathVariable("activityId") String activityId) { 61 | Activity activity = service.searchActivity(activityId); 62 | if (activity == null) { 63 | return Result.error(ResultStatus.RESOURCE_NOT_FOUND); 64 | } 65 | activity.setExpenseList(null); 66 | 67 | return Result.ok(activity); 68 | 69 | } 70 | 71 | /** 72 | * 当前的活动 73 | * 74 | * @return 75 | */ 76 | @Authorization 77 | @GetMapping("/current") 78 | Result getCurrentActivity(@UserId String userId) { 79 | Activity activity = service.getCurrentActivity(userId); 80 | return Result.ok(activity); 81 | } 82 | 83 | /** 84 | * 加入的活动 85 | */ 86 | @Authorization 87 | @GetMapping("/list/joined") 88 | Result getJoinedActivityList(@UserId String userId) { 89 | Activity query = Activity.builder().userId(userId).build(); 90 | return Result.ok(service.queryJoinedActivityList(query)); 91 | } 92 | 93 | @Authorization 94 | @PostMapping 95 | Result insert(@RequestBody Activity activity, @UserId String userId) { 96 | activity.setUserId(userId); 97 | Activity resp = service.insert(activity); 98 | return Result.ok(resp); 99 | } 100 | 101 | 102 | @Authorization 103 | @PatchMapping 104 | Result update(@RequestBody Activity activity, @UserId String userId) { 105 | activity.setUserId(userId); 106 | if (activity.getActivityId() == null) { 107 | return Result.error(ResultStatus.PRIMARY_ID_MISS); 108 | } 109 | 110 | boolean isOwnerActivity = service.isOwnerActivity(activity); 111 | 112 | if (!isOwnerActivity) { 113 | return Result.error(ResultStatus.NOT_OWN_RESOURCE); 114 | } 115 | 116 | return Result.ok(service.update(activity)); 117 | } 118 | 119 | @Authorization 120 | @PostMapping("/exit/{activityId}") 121 | Result exitActivity(@PathVariable("activityId") String activityId, @UserId String userId) { 122 | // 如果这个账本是自己的,则不能退 123 | Activity query = Activity.builder().activityId(activityId).userId(userId).build(); 124 | 125 | boolean ownerActivity = service.isOwnerActivity(query); 126 | if (ownerActivity) { 127 | return Result.error(ResultStatus.OWNER_CANT_EXIT); 128 | } 129 | 130 | service.exitActivity(activityId, userId); 131 | return Result.ok(); 132 | } 133 | 134 | @Authorization 135 | @PostMapping("/join/{activityId}") 136 | Result joinActivity(@PathVariable("activityId") String activityId, @UserId String userId) { 137 | log.info("==> 【加入活动】 用户: {},活动id: {}", userId, activityId); 138 | // = validate = 139 | // 1. 找不找得到这个活动 140 | Activity activity = service.queryActivityByActivityId(activityId); 141 | if (activity == null) { 142 | return Result.error(ResultStatus.RESOURCE_NOT_FOUND); 143 | } 144 | // 2. 是否为本人的活动 145 | if (activity.getUserId().equalsIgnoreCase(userId)) { 146 | return Result.error(ResultStatus.FORBID_JOIN_SELF_ACTIVITY); 147 | } 148 | 149 | // 3. 是否已经加入了 150 | List joinedUserList = activity.getUserList() 151 | .stream() 152 | .filter(user -> user.getUserId().equalsIgnoreCase(userId)) 153 | .toList(); 154 | 155 | if (CollectionUtil.isNotEmpty(joinedUserList)) { 156 | return Result.error(ResultStatus.JOIN_REPEAT); 157 | } 158 | 159 | ActivityUserRel joinQuery = ActivityUserRel.builder().userId(userId).activityId(activityId).build(); 160 | 161 | service.joinActivity(joinQuery); 162 | 163 | return Result.ok(); 164 | } 165 | 166 | @Authorization 167 | @GetMapping("/isOwner") 168 | Result isOwner(@RequestParam("activityId") String activityId, @UserId String userId) { 169 | Activity query = Activity.builder().activityId(activityId).userId(userId).build(); 170 | return Result.ok(service.isOwnerActivity(query)); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/ChartsController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import com.alibaba.excel.EasyExcel; 4 | import com.alibaba.fastjson.JSON; 5 | import com.github.pagehelper.PageInfo; 6 | import com.uuorb.journal.annotation.Authorization; 7 | import com.uuorb.journal.annotation.Log; 8 | import com.uuorb.journal.annotation.UserId; 9 | import com.uuorb.journal.controller.vo.ChartsDataNode; 10 | import com.uuorb.journal.controller.vo.Result; 11 | import com.uuorb.journal.mapper.ChartsMapper; 12 | import com.uuorb.journal.model.Expense; 13 | import com.uuorb.journal.service.ExpenseService; 14 | import jakarta.annotation.Resource; 15 | import jakarta.servlet.http.HttpServletResponse; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | import java.io.IOException; 23 | import java.math.BigDecimal; 24 | import java.net.URLEncoder; 25 | import java.nio.charset.StandardCharsets; 26 | import java.time.DayOfWeek; 27 | import java.time.LocalDate; 28 | import java.util.*; 29 | 30 | @Slf4j 31 | @RestController 32 | @RequestMapping("/charts") 33 | public class ChartsController { 34 | 35 | @Resource 36 | ChartsMapper chartsMapper; 37 | @Resource 38 | ExpenseService expenseService; 39 | 40 | public static List daysOfWeek() { 41 | LocalDate today = LocalDate.now(); 42 | DayOfWeek currentDay = today.getDayOfWeek(); 43 | List daysOfWeek = new ArrayList<>(Arrays.asList("周一", "周二", "周三", "周四", "周五", "周六", "周日")); 44 | 45 | // 找到今天所在的星期索引 46 | int todayIndex = currentDay.getValue() - 1; // DayOfWeek的索引从1开始,且周日为1 47 | 48 | // 生成从“周二”到“周日”,然后回到“周一”的数组 49 | List nextDays = new ArrayList<>(); 50 | for (int i = 0; i < daysOfWeek.size(); i++) { 51 | int dayIndex = (todayIndex + i + 1) % daysOfWeek.size(); // 加1是因为我们要从“明天”开始 52 | nextDays.add(daysOfWeek.get(dayIndex)); 53 | } 54 | 55 | // 输出生成的数组 56 | return nextDays; 57 | } 58 | @Log 59 | 60 | @Authorization 61 | @GetMapping("/weekly/{activityId}") 62 | Result getWeeklyCharts(@PathVariable("activityId") String activityId) { 63 | getInfo(activityId); 64 | List DAYS_OF_WEEK = daysOfWeek(); 65 | 66 | List chartsDataNodes = chartsMapper.queryWeekly(activityId); 67 | 68 | if (chartsDataNodes.size() == 0) { 69 | return Result.ok(); 70 | } 71 | 72 | // 检查并添加缺失的星期几 73 | for (String day : DAYS_OF_WEEK) { 74 | if (!chartsDataNodes.stream().anyMatch(e -> day.equals(e.getName()))) { 75 | ChartsDataNode newDay = new ChartsDataNode(); 76 | newDay.setName(day); 77 | newDay.setValue(BigDecimal.ZERO); 78 | chartsDataNodes.add(newDay); 79 | } 80 | } 81 | 82 | // 确保结果按周一到周日的顺序排列 83 | chartsDataNodes.sort(Comparator.comparing(e -> DAYS_OF_WEEK.indexOf(e.getName()))); 84 | return Result.ok(chartsDataNodes); 85 | } 86 | 87 | private static void getInfo(String activityId) { 88 | log.info("查看可视化:activity:{}", activityId); 89 | } 90 | @Log 91 | 92 | @Authorization 93 | @GetMapping("/weekly/income/{activityId}") 94 | Result getWeeklyChartsIncome(@PathVariable("activityId") String activityId) { 95 | getInfo(activityId); 96 | List DAYS_OF_WEEK = daysOfWeek(); 97 | 98 | List chartsDataNodes = chartsMapper.queryWeeklyIncome(activityId); 99 | if (chartsDataNodes.isEmpty()) { 100 | return Result.ok(); 101 | } 102 | // 检查并添加缺失的星期几 103 | for (String day : DAYS_OF_WEEK) { 104 | if (!chartsDataNodes.stream().anyMatch(e -> day.equals(e.getName()))) { 105 | ChartsDataNode newDay = new ChartsDataNode(); 106 | newDay.setName(day); 107 | newDay.setValue(BigDecimal.ZERO); 108 | chartsDataNodes.add(newDay); 109 | } 110 | } 111 | 112 | // 确保结果按周一到周日的顺序排列 113 | chartsDataNodes.sort(Comparator.comparing(e -> DAYS_OF_WEEK.indexOf(e.getName()))); 114 | return Result.ok(chartsDataNodes); 115 | } 116 | 117 | @Log 118 | 119 | @Authorization 120 | @GetMapping("/weekly/type/{activityId}") 121 | Result getWeeklyChartsGroupByType(@PathVariable("activityId") String activityId) { 122 | getInfo(activityId); 123 | 124 | List chartsDataNodes = chartsMapper.queryGroupByType(activityId); 125 | 126 | return Result.ok(chartsDataNodes); 127 | } 128 | 129 | @Log 130 | 131 | @Authorization 132 | @GetMapping("/export/{activityId}") 133 | public void exportExcel(HttpServletResponse response, @PathVariable("activityId") String activityId) throws IOException {//, @UserId String userId) { 134 | List list = expenseService.queryListUnlimited(activityId); 135 | // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman 136 | response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 137 | response.setCharacterEncoding("utf-8"); 138 | // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 139 | String fileName = URLEncoder.encode("测试", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); 140 | response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); 141 | EasyExcel.write(response.getOutputStream(), Expense.class).sheet("模板").doWrite(list); 142 | } 143 | 144 | @Log 145 | 146 | 147 | @GetMapping("/test") 148 | public String test() { 149 | List list = expenseService.queryListUnlimited("aca120b534f3b04eb8"); 150 | return JSON.toJSONString(list); 151 | } 152 | 153 | 154 | } 155 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.4 9 | 10 | 11 | com.uuorb 12 | journal 13 | 0.0.1-SNAPSHOT 14 | journal 15 | journal 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 17 32 | 8.0.28 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-actuator 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-data-redis 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-validation 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-web 50 | 51 | 52 | org.mybatis.spring.boot 53 | mybatis-spring-boot-starter 54 | 3.0.3 55 | 56 | 57 | com.alibaba 58 | fastjson 59 | 2.0.0 60 | 61 | 62 | javax.xml.bind 63 | jaxb-api 64 | 2.3.0 65 | 66 | 67 | 68 | com.squareup.okhttp3 69 | okhttp 70 | 4.10.0 71 | 72 | 73 | com.squareup.okhttp3 74 | okhttp-sse 75 | 4.10.0 76 | 77 | 78 | 79 | com.sun.xml.bind 80 | jaxb-core 81 | 2.3.0 82 | 83 | 84 | com.huaban 85 | jieba-analysis 86 | 1.0.2 87 | 88 | 89 | 90 | cn.hutool 91 | hutool-all 92 | 5.8.25 93 | 94 | 95 | com.qcloud 96 | cos_api 97 | 5.6.187 98 | 99 | 100 | com.qcloud 101 | cos-sts_api 102 | 3.1.1 103 | 104 | 105 | com.tencentcloudapi 106 | tencentcloud-sdk-java 107 | 108 | 109 | 3.1.551 110 | 111 | 112 | com.alibaba 113 | easyexcel-core 114 | 4.0.3 115 | 116 | 117 | 118 | io.jsonwebtoken 119 | jjwt 120 | 0.9.1 121 | 122 | 123 | org.projectlombok 124 | lombok 125 | true 126 | 127 | 128 | com.github.pagehelper 129 | pagehelper-spring-boot-starter 130 | 2.1.0 131 | 132 | 133 | mysql 134 | mysql-connector-java 135 | ${mysql.version} 136 | 137 | 138 | org.springframework.boot 139 | spring-boot-starter-test 140 | test 141 | 142 | 143 | org.springframework 144 | spring-aspects 145 | 6.0.7 146 | 147 | 148 | org.mybatis.spring.boot 149 | mybatis-spring-boot-starter-test 150 | 3.0.3 151 | test 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | org.springframework.boot 162 | spring-boot-maven-plugin 163 | 164 | 165 | 166 | org.projectlombok 167 | lombok 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/controller/AIController.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.controller; 2 | 3 | import cn.hutool.core.date.DateTime; 4 | import cn.hutool.core.thread.ThreadUtil; 5 | import com.huaban.analysis.jieba.JiebaSegmenter; 6 | import com.huaban.analysis.jieba.SegToken; 7 | import com.uuorb.journal.annotation.Authorization; 8 | import com.uuorb.journal.annotation.Log; 9 | import com.uuorb.journal.annotation.UserId; 10 | import com.uuorb.journal.controller.vo.Result; 11 | import com.uuorb.journal.mapper.ExpenseMapper; 12 | import com.uuorb.journal.model.AIConfig; 13 | import com.uuorb.journal.model.EngelExpense; 14 | import com.uuorb.journal.model.Expense; 15 | import com.uuorb.journal.model.User; 16 | import com.uuorb.journal.service.AiService; 17 | import com.uuorb.journal.service.ExpenseService; 18 | import com.uuorb.journal.service.UserService; 19 | import jakarta.annotation.Resource; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.springframework.http.MediaType; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.web.bind.annotation.*; 24 | import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; 25 | 26 | import java.util.List; 27 | 28 | import static com.uuorb.journal.constant.ResultStatus.AI_FORMAT_ERROR; 29 | 30 | @Slf4j 31 | @RestController 32 | @RequestMapping("/ai") 33 | public class AIController { 34 | 35 | @Resource(name = "kimi") 36 | AiService kimiService; 37 | 38 | @Resource(name = "coze") 39 | AiService cozeService; 40 | 41 | @Resource 42 | ExpenseService expenseService; 43 | 44 | @Resource 45 | UserService userService; 46 | 47 | @Resource 48 | ExpenseMapper expenseMapper; 49 | 50 | /** 51 | * @param sentence 52 | * @param activityId 53 | * @param userId 54 | * @return 55 | */ 56 | @Log 57 | @Authorization 58 | @GetMapping("/format") 59 | Result format(@RequestParam String sentence, @RequestParam String activityId, @UserId String userId) { 60 | log.info("==> 结构化:{} ", sentence); 61 | 62 | // todo: 校验用户和活动是否归属 63 | Expense expense = kimiService.formatExpense(sentence); 64 | 65 | if (expense == null) { 66 | log.error("结构化失败:{}", sentence); 67 | return Result.error(AI_FORMAT_ERROR); 68 | } 69 | 70 | // 填充前端所需的参数 71 | expense.setActivityId(activityId); 72 | expense.setUserId(userId); 73 | expense.setCreateTime(new DateTime()); 74 | expense.setUpdateTime(new DateTime()); 75 | 76 | // 插入DB 77 | ThreadUtil.execAsync(() -> { 78 | expenseService.insertExpenseAndCalcRemainingBudget(expense); 79 | }); 80 | 81 | return Result.ok(expense); 82 | } 83 | 84 | @Log 85 | @GetMapping("/praise") 86 | Result praise(@RequestParam String sentence) { 87 | // 找到自己的AI关系 88 | String response = kimiService.praise(sentence); 89 | return Result.ok(response); 90 | } 91 | 92 | @Log 93 | @Authorization 94 | @GetMapping("/praise/advance") 95 | Result praiseAdvance(@RequestParam String sentence, @RequestParam String activityId, 96 | @UserId String userId) { 97 | // 找到自己的AI关系 98 | User userProfile = userService.getUserByUserId(userId); 99 | AIConfig aiConfig = AIConfig.of(userProfile); 100 | // todo: 关系,称呼,性格 101 | String response = kimiService.praise(sentence, aiConfig); 102 | return Result.ok(response); 103 | } 104 | 105 | /** 106 | * 流式返回 107 | * 108 | * @param sentence 109 | * @param userId 110 | * @return 111 | */ 112 | @Log 113 | @Authorization 114 | @GetMapping("/praise/stream") 115 | ResponseEntity praiseStream(@RequestParam String sentence, @UserId String userId) { 116 | // 找到自己的AI关系 117 | User userProfile = userService.getUserByUserId(userId); 118 | AIConfig aiConfig = AIConfig.of(userProfile); 119 | return ResponseEntity.ok() 120 | .header("content-type", MediaType.TEXT_EVENT_STREAM_VALUE + ";charset=UTF-8") 121 | .body(outputStream -> { 122 | try { 123 | kimiService.praiseStream(sentence, aiConfig, outputStream); 124 | } catch (Exception e) { 125 | // 处理异常 126 | log.error("夸夸失败:{},{}", sentence, e.getMessage()); 127 | } 128 | }); 129 | } 130 | 131 | @Log 132 | @GetMapping("/engel/{activityId}") 133 | ResponseEntity engel(@PathVariable String activityId) { 134 | List expenseList = expenseMapper.queryListBrief(activityId); 135 | return ResponseEntity.ok() 136 | .header("content-type", MediaType.TEXT_EVENT_STREAM_VALUE + ";charset=UTF-8") 137 | .body(outputStream -> { 138 | try { 139 | kimiService.engelExplain(expenseList, outputStream); 140 | } catch (Exception e) { 141 | // todo: 处理异常 142 | log.error(e.getMessage()); 143 | } 144 | }); 145 | } 146 | 147 | @Log 148 | @GetMapping("/tts") 149 | Result tts(@RequestParam String sentence, @RequestParam String activityId, @UserId String userId) { 150 | // 找到自己的AI关系 151 | User userProfile = userService.getUserByUserId(userId); 152 | AIConfig aiConfig = AIConfig.of(userProfile); 153 | 154 | return Result.ok(cozeService.tts(sentence, aiConfig)); 155 | } 156 | 157 | @Log 158 | @Authorization 159 | @GetMapping("/image") 160 | Result generateImage(@RequestParam String model, @RequestParam String description, 161 | @RequestParam String role) { 162 | String result = cozeService.generateImage(model, description, role); 163 | if (result.startsWith("http")) { 164 | return Result.ok(result); 165 | } else { 166 | return Result.error(805, result); 167 | } 168 | } 169 | 170 | @Log 171 | @Authorization 172 | @GetMapping("/greeting") 173 | Result praiseAdvance(@UserId String userId) { 174 | // 找到自己的AI关系 175 | User userProfile = userService.getUserByUserId(userId); 176 | AIConfig aiConfig = AIConfig.of(userProfile); 177 | 178 | // todo: 关系,称呼,性格 179 | String response = kimiService.greet(aiConfig); 180 | return Result.ok(response); 181 | } 182 | 183 | @GetMapping("/format/local") 184 | Result> formatLocal(@RequestParam("sentence") String sentence) { 185 | List process = new JiebaSegmenter().process(sentence, JiebaSegmenter.SegMode.INDEX); 186 | return Result.ok(process); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.http.HttpUtil; 6 | import cn.hutool.jwt.JWT; 7 | import cn.hutool.jwt.JWTUtil; 8 | import com.alibaba.fastjson.JSONObject; 9 | import com.uuorb.journal.config.WechatOpenConfig; 10 | import com.uuorb.journal.mapper.UserMapper; 11 | import com.uuorb.journal.model.User; 12 | import com.uuorb.journal.model.dto.WechatLoginResp; 13 | import com.uuorb.journal.util.IDUtil; 14 | import com.uuorb.journal.util.TokenUtil; 15 | import io.jsonwebtoken.Jwts; 16 | import io.jsonwebtoken.SignatureAlgorithm; 17 | import jakarta.annotation.Resource; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.stereotype.Service; 21 | 22 | import java.security.KeyFactory; 23 | import java.security.NoSuchAlgorithmException; 24 | import java.security.PrivateKey; 25 | import java.security.spec.InvalidKeySpecException; 26 | import java.security.spec.PKCS8EncodedKeySpec; 27 | import java.util.Base64; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | @Slf4j 33 | @Service 34 | public class UserService { 35 | 36 | @Resource 37 | UserMapper userMapper; 38 | 39 | @Resource 40 | private WechatOpenConfig wechatOpenConfig; 41 | 42 | @Value("${apple.key}") 43 | private String APPLE_LOGIN_KEY; 44 | 45 | final String WechatUnionUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"; 46 | 47 | public User getUserByUserId(String userId) { 48 | User userByUserId = userMapper.getUserByUserId(userId); 49 | log.info("user:{}", userByUserId); 50 | return userByUserId; 51 | } 52 | 53 | public List selectUserByPhone(String telephone) { 54 | User query = User.builder().telephone(telephone).build(); 55 | return userMapper.selectUser(query); 56 | } 57 | 58 | public User registerByPhone(String telephone) { 59 | String userId = IDUtil.userId(); 60 | String nickname = "好享用户" + userId.substring(userId.length() - 4); 61 | 62 | User user = User.builder().userId(userId).nickname(nickname).telephone(telephone).build(); 63 | 64 | userMapper.createUser(user); 65 | return user; 66 | } 67 | 68 | public void updateUser(User user) { 69 | userMapper.updateUser(user); 70 | } 71 | 72 | public String loginWithWechat(String code, String platform) { 73 | if (StrUtil.equalsIgnoreCase(platform, "ios")) { 74 | String appid = wechatOpenConfig.getAppid(); 75 | String secret = wechatOpenConfig.getSecret(); 76 | try { 77 | String s = HttpUtil.get(String.format(WechatUnionUrl, appid, secret, code)); 78 | WechatLoginResp wechatLoginResp = JSONObject.parseObject(s, WechatLoginResp.class); 79 | log.info("getWechatLoginResp:{}", wechatLoginResp); 80 | String openid = wechatLoginResp.getOpenid(); 81 | 82 | String unionid = wechatLoginResp.getUnionid(); 83 | if (unionid == null) { 84 | return null; 85 | } 86 | User userQuery = User.builder().unionId(unionid).build(); 87 | // userQuery 88 | List users = userMapper.selectUser(userQuery); 89 | if (CollectionUtil.isEmpty(users)) { 90 | // register 91 | String userId = IDUtil.userId(); 92 | String nickname = "好享用户" + userId.substring(userId.length() - 4); 93 | User user = User.builder() 94 | .userId(userId) 95 | .nickname(nickname) 96 | .openid(openid) 97 | .unionId(unionid) 98 | .build(); 99 | userMapper.createUser(user); 100 | return TokenUtil.generateToken(userId); 101 | } else { 102 | User user = users.get(0); 103 | String token = TokenUtil.generateToken(user.getUserId()); 104 | return token; 105 | } 106 | } catch (Exception e) { 107 | return null; 108 | } 109 | 110 | } 111 | return null; 112 | 113 | } 114 | 115 | public String loginWithApple(String code) throws NoSuchAlgorithmException, InvalidKeySpecException { 116 | String client_id = "com.uuorb.journal"; // 被授权的APP ID 117 | Map header = new HashMap(); 118 | header.put("kid", "QH4XFS4JS4"); // 参考后台配置 119 | Map claims = new HashMap(); 120 | claims.put("iss", "BVNS39X4QM"); // 参考后台配置 team id 121 | long now = System.currentTimeMillis() / 1000; 122 | claims.put("iat", now); 123 | claims.put("exp", now + 86400 * 30); // 最长半年,单位秒 124 | claims.put("aud", "https://appleid.apple.com"); // 默认值 125 | claims.put("sub", client_id); 126 | PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(readKey()); 127 | KeyFactory keyFactory = KeyFactory.getInstance("EC"); 128 | PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); 129 | String client_secret = Jwts.builder() 130 | .setHeader(header) 131 | .setClaims(claims) 132 | .signWith(SignatureAlgorithm.ES256, privateKey) 133 | .compact(); 134 | 135 | String url = "https://appleid.apple.com/auth/token"; 136 | 137 | // POST 请求 138 | Map form = new HashMap(); 139 | form.put("client_id", client_id); 140 | form.put("client_secret", client_secret); 141 | form.put("code", code); 142 | form.put("grant_type", "authorization_code"); 143 | form.put("redirect_uri", "https://jounral.aceword.xyz/api/token/apple"); // 回调地址 144 | String response = HttpUtil.post(url, form); 145 | JSONObject jsonObject = JSONObject.parseObject(response); 146 | String idToken = (String)jsonObject.get("id_token"); 147 | 148 | JWT userJwtFromApple = JWTUtil.parseToken(idToken); 149 | log.info("apple:{}", idToken); 150 | 151 | log.info("apple:{}", userJwtFromApple.getPayloads()); 152 | String sub = (String)userJwtFromApple.getPayload("sub"); 153 | if (sub == null) { 154 | return null; 155 | } 156 | User userQuery = User.builder().appleId(sub).build(); 157 | List users = userMapper.selectUser(userQuery); 158 | if (CollectionUtil.isEmpty(users)) { 159 | // register 160 | String userId = IDUtil.userId(); 161 | String nickname = "好享用户" + userId.substring(userId.length() - 4); 162 | User user = User.builder().userId(userId).nickname(nickname).appleId(sub).build(); 163 | userMapper.createUser(user); 164 | return TokenUtil.generateToken(userId); 165 | } else { 166 | User user = users.get(0); 167 | String token = TokenUtil.generateToken(user.getUserId()); 168 | return token; 169 | } 170 | } 171 | 172 | // 秘钥读取 173 | public byte[] readKey() { 174 | 175 | return Base64.getDecoder().decode(APPLE_LOGIN_KEY); //Base64.getDecoder().decode(temp); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/KimiUtils.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.http.ContentType; 6 | import cn.hutool.http.Header; 7 | import cn.hutool.http.HttpRequest; 8 | import cn.hutool.http.Method; 9 | import cn.hutool.json.JSONArray; 10 | import cn.hutool.json.JSONObject; 11 | import cn.hutool.json.JSONUtil; 12 | import com.uuorb.journal.model.kimi.KimiMessage; 13 | import lombok.NonNull; 14 | import lombok.SneakyThrows; 15 | import okhttp3.*; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.stereotype.Component; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | import java.util.List; 24 | import java.util.Optional; 25 | 26 | @Component 27 | public class KimiUtils { 28 | 29 | @Value("${kimi.key}") 30 | private String KIMI_API_KEY; 31 | 32 | private static final String MODELS_URL = "https://api.moonshot.cn/v1/models"; 33 | 34 | private static final String FILES_URL = "https://api.moonshot.cn/v1/files"; 35 | 36 | private static final String ESTIMATE_TOKEN_COUNT_URL = "https://api.moonshot.cn/v1/tokenizers/estimate-token-count"; 37 | 38 | private static final String CHAT_COMPLETION_URL = "https://api.moonshot.cn/v1/chat/completions"; 39 | 40 | public String getModelList() { 41 | return getCommonRequest(MODELS_URL).execute().body(); 42 | } 43 | 44 | public String uploadFile(@NonNull File file) { 45 | return getCommonRequest(FILES_URL).method(Method.POST) 46 | .header("purpose", "file-extract") 47 | .form("file", file) 48 | .execute() 49 | .body(); 50 | } 51 | 52 | public String getFileList() { 53 | return getCommonRequest(FILES_URL).execute().body(); 54 | } 55 | 56 | public String deleteFile(@NonNull String fileId) { 57 | return getCommonRequest(FILES_URL + "/" + fileId).method(Method.DELETE).execute().body(); 58 | } 59 | 60 | public String getFileDetail(@NonNull String fileId) { 61 | return getCommonRequest(FILES_URL + "/" + fileId).execute().body(); 62 | } 63 | 64 | public String getFileContent(@NonNull String fileId) { 65 | return getCommonRequest(FILES_URL + "/" + fileId + "/content").execute().body(); 66 | } 67 | 68 | public String estimateTokenCount(@NonNull String model, @NonNull List messages) { 69 | String requestBody = new JSONObject().putOpt("model", model).putOpt("messages", messages).toString(); 70 | return getCommonRequest(ESTIMATE_TOKEN_COUNT_URL).method(Method.POST) 71 | .header(Header.CONTENT_TYPE, ContentType.JSON.getValue()) 72 | .body(requestBody) 73 | .execute() 74 | .body(); 75 | } 76 | 77 | @SneakyThrows 78 | public void chatInStream(@NonNull String model, @NonNull List messages, OutputStream outputStream) { 79 | String requestBody = new JSONObject().putOpt("model", model) 80 | .putOpt("messages", messages) 81 | .putOpt("stream", true) 82 | .toString(); 83 | Request okhttpRequest = new Request.Builder().url(CHAT_COMPLETION_URL) 84 | .post(RequestBody.create(requestBody, MediaType.get(ContentType.JSON.getValue()))) 85 | .addHeader("Authorization", KIMI_API_KEY) 86 | .build(); 87 | Call call = new OkHttpClient().newCall(okhttpRequest); 88 | Response okhttpResponse = call.execute(); 89 | BufferedReader reader = new BufferedReader(okhttpResponse.body().charStream()); 90 | String line; 91 | while ((line = reader.readLine()) != null) { 92 | if (StrUtil.isBlank(line)) { 93 | continue; 94 | } 95 | if (JSONUtil.isTypeJSON(line)) { 96 | Optional.of(JSONUtil.parseObj(line)) 97 | .map(x -> x.getJSONObject("error")) 98 | .map(x -> x.getStr("message")) 99 | .ifPresent(x -> { 100 | try { 101 | outputStream.write(x.getBytes()); 102 | outputStream.flush(); 103 | } catch (IOException e) { 104 | throw new RuntimeException(e); 105 | } 106 | }); 107 | return; 108 | } 109 | line = StrUtil.replace(line, "data: ", StrUtil.EMPTY); 110 | if (StrUtil.equals("[DONE]", line) || !JSONUtil.isTypeJSON(line)) { 111 | return; 112 | } 113 | Optional.of(JSONUtil.parseObj(line)) 114 | .map(x -> x.getJSONArray("choices")) 115 | .filter(CollUtil::isNotEmpty) 116 | .map(x -> (JSONObject)x.get(0)) 117 | .map(x -> x.getJSONObject("delta")) 118 | .map(x -> x.getStr("content")) 119 | .ifPresent(x -> { 120 | try { 121 | outputStream.write(x.getBytes()); 122 | outputStream.flush(); 123 | } catch (IOException e) { 124 | throw new RuntimeException(e); 125 | } 126 | }); 127 | } 128 | } 129 | 130 | /** 131 | * 相关文档:{@link 点击查看文档 } 132 | * 133 | * @param model: 调用kimi的模型 134 | * @param messages: 消息列表 135 | * @return KimiMessage 136 | */ 137 | @SneakyThrows 138 | public KimiMessage chat(@NonNull String model, @NonNull List messages) { 139 | String requestBody = new JSONObject().putOpt("model", model) 140 | .putOpt("messages", messages) 141 | .putOpt("type", "json_object") 142 | .putOpt("stream", false) 143 | .toString(); 144 | 145 | Request okhttpRequest = new Request.Builder().url(CHAT_COMPLETION_URL) 146 | .post(RequestBody.create(requestBody, MediaType.get("application/json; charset=utf-8"))) 147 | .addHeader("Authorization", KIMI_API_KEY) 148 | .build(); 149 | 150 | OkHttpClient client = new OkHttpClient(); 151 | try (Response okhttpResponse = client.newCall(okhttpRequest).execute()) { 152 | if (!okhttpResponse.isSuccessful()) { 153 | throw new IOException("Unexpected code " + okhttpResponse); 154 | } ; 155 | 156 | assert okhttpResponse.body() != null; 157 | String responseBody = okhttpResponse.body().string(); 158 | JSONArray choices = new JSONObject(responseBody).getJSONArray("choices"); 159 | 160 | if (choices == null || choices.isEmpty()) { 161 | throw new IllegalStateException("No choices found in response"); 162 | } 163 | 164 | JSONObject choice = (JSONObject)choices.get(0); 165 | String messageJson = choice.getStr("message", null); 166 | if (messageJson == null) { 167 | throw new IllegalStateException("Message is missing in the choice"); 168 | } 169 | return JSONUtil.toBean(messageJson, KimiMessage.class); 170 | } 171 | } 172 | 173 | private HttpRequest getCommonRequest(@NonNull String url) { 174 | return HttpRequest.of(url).header(Header.AUTHORIZATION, "Bearer " + KIMI_API_KEY); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/service/CozeService.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.service; 2 | 3 | import cn.hutool.core.util.ArrayUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.json.JSONObject; 6 | import com.uuorb.journal.model.AIConfig; 7 | import com.uuorb.journal.model.EngelExpense; 8 | import com.uuorb.journal.model.Expense; 9 | import lombok.extern.slf4j.Slf4j; 10 | import okhttp3.*; 11 | import org.apache.poi.util.NotImplemented; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.io.IOException; 17 | import java.io.OutputStream; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | import static cn.hutool.core.text.CharSequenceUtil.firstNonEmpty; 22 | 23 | @Service("coze") 24 | @Slf4j 25 | public class CozeService implements AiService { 26 | 27 | @Value("${coze.token}") 28 | private String TOKEN; 29 | 30 | private static String GENERATE_IMAGE_WORKFLOW_ID = "7425232407248322598"; 31 | 32 | private static String TTS_WORKFLOW_ID = "7427813081563414554"; 33 | 34 | private static String EXPENSE_FORMAT_WORKFLOW_ID = "7427869736202944549"; 35 | 36 | private static final String WORKFLOW_URL = "https://api.coze.cn/v1/workflow/run"; 37 | 38 | @Override 39 | public Expense formatExpense(String naturalSentence) { 40 | OkHttpClient client = new OkHttpClient(); 41 | MediaType JSON = MediaType.get("application/json; charset=utf-8"); 42 | JSONObject req = new JSONObject(); 43 | JSONObject params = new JSONObject(); 44 | params.set("text", naturalSentence); 45 | req.set("workflow_id", EXPENSE_FORMAT_WORKFLOW_ID); 46 | req.set("parameters", params); 47 | 48 | RequestBody body = RequestBody.create(req.toJSONString(0), JSON); 49 | Request request = new Request.Builder().url(WORKFLOW_URL) 50 | .post(body) 51 | .addHeader("Authorization", TOKEN) 52 | .addHeader("Content-Type", "application/json") 53 | .build(); 54 | 55 | try { 56 | Response response = client.newCall(request).execute(); 57 | if (response.isSuccessful()) { 58 | String responseData = response.body().string(); 59 | JSONObject data = new JSONObject(responseData); 60 | JSONObject jsonObject = data.getJSONObject("data");// new JSONObject(responseData); 61 | 62 | String output = jsonObject.getStr("output"); 63 | 64 | try { 65 | return com.alibaba.fastjson2.JSONObject.parseObject(output, Expense.class); 66 | } catch (Exception e) { 67 | return null; 68 | } 69 | } else { 70 | log.error("formatExpense,请求失败:{},{}", response.code(), naturalSentence); 71 | // System.out.println("请求失败: " + response.code()); 72 | return null; 73 | } 74 | } catch (IOException e) { 75 | return null; 76 | } 77 | 78 | } 79 | 80 | @Override 81 | public String praise(String sentence) { 82 | return ""; 83 | } 84 | 85 | @Override 86 | public String praise(String sentence, AIConfig config) { 87 | return ""; 88 | } 89 | 90 | @Override 91 | public String greet(AIConfig aiConfig) { 92 | return ""; 93 | } 94 | 95 | @Override 96 | public String generateImage(String model, String description, String role) { 97 | String result = workflowGenerateImage(role, description, model); 98 | 99 | return result; 100 | } 101 | 102 | @Override 103 | public void praiseStream(String sentence, AIConfig config, OutputStream outputStream) { 104 | 105 | } 106 | 107 | @Override 108 | public String tts(String sentence, AIConfig aiConfig) { 109 | OkHttpClient client = new OkHttpClient(); 110 | MediaType JSON = MediaType.get("application/json; charset=utf-8"); 111 | JSONObject req = getEntries(sentence, aiConfig); 112 | 113 | RequestBody body = RequestBody.create(req.toJSONString(0), JSON); 114 | Request request = new Request.Builder().url(WORKFLOW_URL) 115 | .post(body) 116 | .addHeader("Authorization", TOKEN) 117 | .addHeader("Content-Type", "application/json") 118 | .build(); 119 | 120 | try { 121 | Response response = client.newCall(request).execute(); 122 | if (response.isSuccessful()) { 123 | assert response.body() != null; 124 | String responseData = response.body().string(); 125 | JSONObject data = new JSONObject(responseData); 126 | JSONObject jsonObject = data.getJSONObject("data");// new JSONObject(responseData); 127 | JSONObject data1 = jsonObject.getJSONObject("data1"); 128 | JSONObject data2 = jsonObject.getJSONObject("data2"); 129 | JSONObject data3 = jsonObject.getJSONObject("data3"); 130 | JSONObject data4 = jsonObject.getJSONObject("data4"); 131 | 132 | JSONObject existObj = firstNotNullJsonObj(data1, data2, data3, data4); 133 | if (existObj != null) { 134 | return existObj.getStr("url"); 135 | } 136 | } else { 137 | System.out.println("请求失败: " + response.code()); 138 | } 139 | return "请求失败"; 140 | } catch (IOException e) { 141 | return "请求失败"; 142 | } 143 | } 144 | 145 | @NotImplemented 146 | @Override 147 | public void engelExplain(List expenseList, OutputStream outputStream) { 148 | log.error("NOT IMPLEMENTED"); 149 | } 150 | 151 | private static @NotNull JSONObject getEntries(String sentence, AIConfig aiConfig) { 152 | JSONObject req = new JSONObject(); 153 | req.set("workflow_id", TTS_WORKFLOW_ID); 154 | 155 | JSONObject params = new JSONObject(); 156 | 157 | String relationship = aiConfig.getRelationship(); 158 | if (StrUtil.equals(relationship, "女朋友")) { 159 | params.set("model", "yujie"); 160 | } 161 | 162 | if (StrUtil.equals(relationship, "女儿")) { 163 | params.set("model", "nvsheng"); 164 | } 165 | 166 | if (StrUtil.equals(relationship, "儿子")) { 167 | params.set("model", "qingnian"); 168 | } 169 | 170 | if (StrUtil.equals(relationship, "男朋友")) { 171 | params.set("model", "ahu"); 172 | } 173 | 174 | params.set("text", sentence); 175 | req.set("parameters", params); 176 | return req; 177 | } 178 | 179 | JSONObject firstNotNullJsonObj(JSONObject... arrs) { 180 | return ArrayUtil.firstMatch(Objects::nonNull, arrs); 181 | } 182 | 183 | public String workflowGenerateImage(String relationship, String description, String model) { 184 | OkHttpClient client = new OkHttpClient(); 185 | 186 | MediaType JSON = MediaType.get("application/json; charset=utf-8"); 187 | JSONObject req = new JSONObject(); 188 | JSONObject params = new JSONObject(); 189 | params.set("relationship", relationship); 190 | params.set("description", description); 191 | params.set("model", model); 192 | req.set("workflow_id", GENERATE_IMAGE_WORKFLOW_ID); 193 | req.set("parameters", params); 194 | 195 | RequestBody body = RequestBody.create(req.toJSONString(0), JSON); 196 | Request request = new Request.Builder().url(WORKFLOW_URL) 197 | .post(body) 198 | .addHeader("Authorization", TOKEN) 199 | .addHeader("Content-Type", "application/json") 200 | .build(); 201 | 202 | try { 203 | Response response = client.newCall(request).execute(); 204 | if (response.isSuccessful()) { 205 | String responseData = response.body().string(); 206 | System.out.println(responseData); 207 | JSONObject data = new JSONObject(responseData); 208 | JSONObject jsonObject = data.getJSONObject("data");// new JSONObject(responseData); 209 | 210 | String output1 = jsonObject.getStr("output1"); 211 | String output2 = jsonObject.getStr("output2"); 212 | String msg1 = jsonObject.getStr("msg1"); 213 | String msg2 = jsonObject.getStr("msg2"); 214 | 215 | return firstNonEmpty(output1, output2, msg1, msg2); 216 | } else { 217 | System.out.println("请求失败: " + response.code()); 218 | return "请求失败"; 219 | } 220 | } catch (IOException e) { 221 | return "请求失败"; 222 | } 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /src/main/resources/mapper/ActivityMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 57 | 58 | SELECT activity.id, 59 | activity.user_id, 60 | activity.activity_id, 61 | activity.create_time, 62 | activity.update_time, 63 | activity.activity_name, 64 | activity.budget, 65 | activity.remaining_budget, 66 | activity.activated, 67 | (SELECT nickname FROM users u WHERE u.user_id = activity.user_id) as creatorName, 68 | users.id as UID, 69 | users.user_id as u_userId, 70 | users.nickname, 71 | users.avatar_url as avatarUrl, 72 | users.openid, 73 | users.union_id as unionId, 74 | users.telephone as u_telephone, 75 | users.current_activity_id as u_currentActivityId, 76 | users.vip, 77 | users.vip_expire_time as vipExpireTime, 78 | users.last_login_time as lastLoginTime, 79 | users.create_time as u_createTime, 80 | users.update_time as u_updateTime, 81 | (SELECT SUM(price) 82 | FROM expense 83 | WHERE expense.activity_id = activity.activity_id AND expense.positive = 0 ) as total_expense, 84 | (SELECT SUM(price) 85 | FROM expense 86 | WHERE expense.activity_id = activity.activity_id AND expense.positive = 1 ) as total_income 87 | 88 | 89 | 96 | 97 | 107 | 114 | 124 | 125 | 131 | 137 | 138 | 139 | update activity 140 | SET activated = 0 141 | WHERE user_id = #{userId} 142 | AND activity_id != #{activityId} 143 | 144 | 145 | 146 | UPDATE activity 147 | 148 | 149 | budget = #{budget}, 150 | 151 | 152 | remaining_budget = #{remainingBudget}, 153 | 154 | 155 | activity_name = #{activityName}, 156 | 157 | 158 | activated = #{activated}, 159 | 160 | 161 | where activity_id = #{activityId} 162 | 163 | 164 | 165 | UPDATE activity 166 | SET remaining_budget = (budget 167 | - 168 | COALESCE((SELECT SUM(price) 169 | FROM expense 170 | WHERE expense.activity_id = #{activityId} AND expense.positive = 0),0) 171 | + COALESCE((SELECT SUM(price) FROM expense WHERE expense.activity_id = #{activityId} AND expense.positive = 1) 172 | ,0)) 173 | WHERE activity_id = #{activityId} 174 | 175 | 176 | 177 | INSERT INTO activity(activity_id, activity_name, budget, user_id, remaining_budget) 178 | VALUES (#{activityId}, #{activityName}, #{budget}, #{userId}, #{remainingBudget}); 179 | 180 | 181 | 183 | INSERT INTO activity_user_rel(activity_id, user_id) 184 | VALUES (#{activityId}, #{userId}) 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/TokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.uuorb.journal.constant.JwtConstant; 5 | import io.jsonwebtoken.*; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.time.Duration; 9 | import java.util.Base64; 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * @ClassName: JwtTokenUtil 16 | * @Author: 小霍 17 | * @UpdateUser: 小霍 18 | * @Version: 0.0.1 19 | */ 20 | @Slf4j 21 | public class TokenUtil { 22 | private static String secretKey; 23 | private static Duration accessTokenExpireTime; 24 | private static Duration refreshTokenExpireTime; 25 | private static Duration refreshTokenExpireAppTime; 26 | private static String issuer; 27 | 28 | public static void setTokenSettings(TokenSettings tokenSettings) { 29 | secretKey = tokenSettings.getSecretKey(); 30 | accessTokenExpireTime = tokenSettings.getAccessTokenExpireTime(); 31 | refreshTokenExpireTime = tokenSettings.getRefreshTokenExpireTime(); 32 | refreshTokenExpireAppTime = tokenSettings.getRefreshTokenExpireAppTime(); 33 | issuer = tokenSettings.getIssuer(); 34 | } 35 | 36 | public static String generateToken(String subject, Map claims) { 37 | return generateToken(issuer, subject, claims, accessTokenExpireTime.toMillis(), secretKey); 38 | } 39 | 40 | 41 | public static String generateToken(String userId) { 42 | String subject = "com.uuorb.journal"; 43 | Map claims = new HashMap<>(); 44 | claims.put(JwtConstant.JWT_USER_ID, userId); 45 | return generateToken(issuer, subject, claims, accessTokenExpireTime.toMillis(), secretKey); 46 | } 47 | public static byte[] strToByteArray(String str) { 48 | if (str == null) { return null; } byte[] byteArray = str.getBytes(); return byteArray; } 49 | 50 | /** 51 | * 签发token 52 | * 53 | * @param issuer 签发人 54 | * @param subject 代表这个JWT的主体,即它的所有人 一般是用户id 55 | * @param claims 存储在JWT里面的信息 一般放些用户的权限/角色信息 56 | * @param ttlMillis 有效时间(毫秒) 57 | * @return java.lang.String 58 | * @throws 59 | * @Author: 小霍 60 | * @UpdateUser: 61 | * @Version: 0.0.1 62 | */ 63 | public static String generateToken(String issuer, String subject, Map claims, long ttlMillis, String secret) { 64 | 65 | SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 66 | 67 | long nowMillis = System.currentTimeMillis(); 68 | Date now = new Date(nowMillis); 69 | 70 | // byte[] signingKey = DatatypeConverter.parseBase64Binary(secret); 71 | byte[] signingKey = strToByteArray(secret); 72 | 73 | 74 | 75 | JwtBuilder builder = Jwts.builder(); 76 | builder.setHeaderParam("typ", "JWT"); 77 | if (null != claims) { 78 | builder.setClaims(claims); 79 | } 80 | if (StrUtil.isNotEmpty(issuer)) { 81 | builder.setSubject(subject); 82 | } 83 | if (StrUtil.isNotEmpty(issuer)) { 84 | builder.setIssuer(issuer); 85 | } 86 | builder.setIssuedAt(now); 87 | if (ttlMillis >= 0) { 88 | long expMillis = nowMillis + ttlMillis; 89 | Date exp = new Date(expMillis); 90 | builder.setExpiration(exp); 91 | } 92 | builder.signWith(signatureAlgorithm, signingKey); 93 | return builder.compact(); 94 | } 95 | 96 | // 上面我们已经有生成 access_token 的方法,下面加入生成 refresh_token 的方法(PC 端过期时间短一些) 97 | 98 | /** 99 | * 生产 PC refresh_token 100 | * 101 | * @param subject 102 | * @param claims 103 | * @return java.lang.String 104 | * @throws 105 | * @Author: 小霍 106 | * @UpdateUser: 107 | * @Version: 0.0.1 108 | */ 109 | public static String getRefreshToken(String subject, Map claims) { 110 | return generateToken(issuer, subject, claims, refreshTokenExpireTime.toMillis(), secretKey); 111 | } 112 | 113 | /** 114 | * 生产 App端 refresh_token 115 | * 116 | * @param subject 117 | * @param claims 118 | * @return java.lang.String 119 | * @throws 120 | * @Author: 小霍 121 | * @UpdateUser: 122 | * @Version: 0.0.1 123 | */ 124 | public static String getRefreshAppToken(String subject, Map claims) { 125 | return generateToken(issuer, subject, claims, refreshTokenExpireAppTime.toMillis(), secretKey); 126 | } 127 | 128 | /** 129 | * 从令牌中获取数据声明 130 | * 131 | * @param token 132 | * @return io.jsonwebtoken.Claims 133 | * @throws 134 | * @Author: 小霍 135 | * @UpdateUser: 136 | * @Version: 0.0.1 137 | */ 138 | public static Claims getClaimsFromToken(String token) { 139 | Claims claims = null; 140 | try { 141 | byte[] signingKey = strToByteArray(secretKey); 142 | 143 | claims = Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token).getBody(); 144 | } catch (Exception e) { 145 | if (e instanceof ClaimJwtException) { 146 | claims = ((ClaimJwtException) e).getClaims(); 147 | } 148 | } 149 | return claims; 150 | } 151 | 152 | public static Claims getClaimsFromToken(String token, String secret) { 153 | Claims claims = null; 154 | try { 155 | claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 156 | } catch (Exception e) { 157 | if (e instanceof ClaimJwtException) { 158 | claims = ((ClaimJwtException) e).getClaims(); 159 | } 160 | } 161 | return claims; 162 | } 163 | 164 | /** 165 | * 获取用户openid 166 | * 167 | * @param token 168 | * @return java.lang.String 169 | * @throws 170 | * @Author: 小霍 171 | * @UpdateUser: 172 | * @Version: 0.0.1 173 | */ 174 | public static String getUserOpenid(String token) { 175 | String openid = null; 176 | try { 177 | Claims claims = getClaimsFromToken(token); 178 | openid = (String) claims.get(JwtConstant.JWT_USER_OPENID); 179 | } catch (Exception e) { 180 | log.error("error={}", e); 181 | } 182 | return openid; 183 | } 184 | 185 | /** 186 | * 获取用户openid 187 | * 188 | * @param token 189 | * @return java.lang.String 190 | * @throws 191 | * @Author: 小霍 192 | * @UpdateUser: 193 | * @Version: 0.0.1 194 | */ 195 | public static String getUserId(String token) { 196 | String id = null; 197 | try { 198 | Claims claims = getClaimsFromToken(token); 199 | id = (String) claims.get(JwtConstant.JWT_USER_ID); 200 | } catch (Exception e) { 201 | log.error("error={}", e); 202 | } 203 | return id; 204 | } 205 | 206 | 207 | /** 208 | * 验证token 是否过期 209 | * 210 | * @param token 211 | * @return java.lang.Boolean 212 | * @throws 213 | * @Author: 小霍 214 | * @UpdateUser: 215 | * @Version: 0.0.1 216 | */ 217 | public static Boolean isTokenExpired(String token) { 218 | try { 219 | Claims claims = getClaimsFromToken(token); 220 | Date expiration = claims.getExpiration(); 221 | return expiration.before(new Date()); 222 | } catch (Exception e) { 223 | log.error("error={}", e); 224 | return true; 225 | } 226 | } 227 | 228 | /** 229 | * 校验令牌,并查看当前的openid是否为数据库中的openid 230 | * 231 | * @param token 232 | * @return java.lang.Boolean 233 | * @throws 234 | * @Author: 小霍 235 | * @UpdateUser: 236 | * @Version: 0.0.1 237 | */ 238 | public static Boolean validateToken(String token) { 239 | Claims claimsFromToken = getClaimsFromToken(token); 240 | return (null != claimsFromToken && !isTokenExpired(token)); 241 | } 242 | 243 | 244 | /** 245 | * 校验令牌是否为管理员 246 | * 247 | * @param token 248 | * @return java.lang.Boolean 249 | * @throws 250 | * @Author: 小霍 251 | * @UpdateUser: 252 | * @Version: 0.0.1 253 | */ 254 | public static Boolean validateAdmin(String token) { 255 | Claims claimsFromToken = getClaimsFromToken(token); 256 | String role = (String) claimsFromToken.get(JwtConstant.ROLES_INFOS_KEY); 257 | System.out.println(role); 258 | return (null != claimsFromToken && !isTokenExpired(token) && StrUtil.equals(role, JwtConstant.ROLES_ADMIN_VALUE)); 259 | } 260 | 261 | /** 262 | * 刷新token 263 | * 264 | * @param refreshToken 265 | * @param claims 主动去刷新的时候 改变JWT payload 内的信息 266 | * @return java.lang.String 267 | * @throws 268 | * @Author: 小霍 269 | * @UpdateUser: 270 | * @Version: 0.0.1 271 | */ 272 | public static String refreshToken(String refreshToken, Map claims) { 273 | String refreshedToken; 274 | try { 275 | Claims parserclaims = getClaimsFromToken(refreshToken); 276 | /** 277 | * 刷新token的时候如果为空说明原先的 用户信息不变 所以就引用上个token里的内容 278 | */ 279 | if (null == claims) { 280 | claims = parserclaims; 281 | } 282 | refreshedToken = generateToken(parserclaims.getIssuer(), parserclaims.getSubject(), claims, accessTokenExpireTime.toMillis(), secretKey); 283 | } catch (Exception e) { 284 | refreshedToken = null; 285 | log.error("error={}", e); 286 | } 287 | return refreshedToken; 288 | } 289 | 290 | /** 291 | * 获取token的剩余过期时间 292 | * 293 | * @param token 294 | * @return long 295 | * @throws 296 | * @Author: 小霍 297 | * @UpdateUser: 298 | * @Version: 0.0.1 299 | */ 300 | public static long getRemainingTime(String token) { 301 | long result = 0; 302 | try { 303 | long nowMillis = System.currentTimeMillis(); 304 | result = getClaimsFromToken(token).getExpiration().getTime() - nowMillis; 305 | } catch (Exception e) { 306 | log.error("error={}", e); 307 | } 308 | return result; 309 | } 310 | 311 | 312 | } 313 | 314 | 315 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /src/main/java/com/uuorb/journal/util/RedisUtil.java: -------------------------------------------------------------------------------- 1 | package com.uuorb.journal.util; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * Redis工具类 14 | * 15 | * @author ZENG.XIAO.YAN 16 | * @date 2018年6月7日 17 | */ 18 | @Component 19 | public final class RedisUtil { 20 | 21 | @Autowired 22 | private RedisTemplate redisTemplate; 23 | // =============================common============================ 24 | 25 | /** 26 | * 指定缓存失效时间 27 | * 28 | * @param key 键 29 | * @param time 时间(秒) 30 | * @return 31 | */ 32 | public boolean expire(String key, long time) { 33 | try { 34 | if (time > 0) { 35 | redisTemplate.expire(key, time, TimeUnit.SECONDS); 36 | } 37 | return true; 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | return false; 41 | } 42 | } 43 | 44 | /** 45 | * 根据key 获取过期时间 46 | * 47 | * @param key 键 不能为null 48 | * @return 时间(秒) 返回0代表为永久有效 49 | */ 50 | public long getExpire(String key) { 51 | return redisTemplate.getExpire(key, TimeUnit.SECONDS); 52 | } 53 | 54 | /** 55 | * 判断key是否存在 56 | * 57 | * @param key 键 58 | * @return true 存在 false不存在 59 | */ 60 | public boolean hasKey(String key) { 61 | try { 62 | return redisTemplate.hasKey(key); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | return false; 66 | } 67 | } 68 | 69 | /** 70 | * 删除缓存 71 | * 72 | * @param key 传一个值 73 | */ 74 | @SuppressWarnings("unchecked") 75 | public void del(String key) { 76 | redisTemplate.delete(key); 77 | } 78 | 79 | /** 80 | * 清除所有缓存 81 | */ 82 | public void delAll() { 83 | Set keys = redisTemplate.keys("*"); 84 | redisTemplate.delete(keys); 85 | } 86 | 87 | // ============================String============================= 88 | 89 | /** 90 | * 普通缓存获取 91 | * 92 | * @param key 键 93 | * @return 值 94 | */ 95 | public Object get(String key) { 96 | return key == null ? null : redisTemplate.opsForValue().get(key); 97 | } 98 | 99 | /** 100 | * 普通缓存放入 101 | * 102 | * @param key 键 103 | * @param value 值 104 | * @return true成功 false失败 105 | */ 106 | public boolean set(String key, Object value) { 107 | try { 108 | redisTemplate.opsForValue().set(key, value); 109 | return true; 110 | } catch (Exception e) { 111 | e.printStackTrace(); 112 | return false; 113 | } 114 | } 115 | 116 | /** 117 | * 普通缓存放入并设置时间 118 | * 119 | * @param key 键 120 | * @param value 值 121 | * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 122 | * @return true成功 false 失败 123 | */ 124 | public boolean set(String key, Object value, long time) { 125 | try { 126 | if (time > 0) { 127 | redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); 128 | } else { 129 | set(key, value); 130 | } 131 | return true; 132 | } catch (Exception e) { 133 | e.printStackTrace(); 134 | return false; 135 | } 136 | } 137 | 138 | /** 139 | * 递增 140 | * 141 | * @param key 键 142 | * @param delta 要增加几(大于0) 143 | * @return 144 | */ 145 | public long incr(String key, long delta) { 146 | if (delta < 0) { 147 | throw new RuntimeException("递增因子必须大于0"); 148 | } 149 | return redisTemplate.opsForValue().increment(key, delta); 150 | } 151 | 152 | /** 153 | * 递减 154 | * 155 | * @param key 键 156 | * @param delta 要减少几(小于0) 157 | * @return 158 | */ 159 | public long decr(String key, long delta) { 160 | if (delta < 0) { 161 | throw new RuntimeException("递减因子必须大于0"); 162 | } 163 | return redisTemplate.opsForValue().increment(key, -delta); 164 | } 165 | 166 | // ================================Map================================= 167 | 168 | /** 169 | * HashGet 170 | * 171 | * @param key 键 不能为null 172 | * @param item 项 不能为null 173 | * @return 值 174 | */ 175 | public Object hget(String key, String item) { 176 | return redisTemplate.opsForHash().get(key, item); 177 | } 178 | 179 | /** 180 | * 获取hashKey对应的所有键值 181 | * 182 | * @param key 键 183 | * @return 对应的多个键值 184 | */ 185 | public Map hmget(String key) { 186 | return redisTemplate.opsForHash().entries(key); 187 | } 188 | 189 | /** 190 | * HashSet 191 | * 192 | * @param key 键 193 | * @param map 对应多个键值 194 | * @return true 成功 false 失败 195 | */ 196 | public boolean hmset(String key, Map map) { 197 | try { 198 | redisTemplate.opsForHash().putAll(key, map); 199 | return true; 200 | } catch (Exception e) { 201 | e.printStackTrace(); 202 | return false; 203 | } 204 | } 205 | 206 | /** 207 | * HashSet 并设置时间 208 | * 209 | * @param key 键 210 | * @param map 对应多个键值 211 | * @param time 时间(秒) 212 | * @return true成功 false失败 213 | */ 214 | public boolean hmset(String key, Map map, long time) { 215 | try { 216 | redisTemplate.opsForHash().putAll(key, map); 217 | if (time > 0) { 218 | expire(key, time); 219 | } 220 | return true; 221 | } catch (Exception e) { 222 | e.printStackTrace(); 223 | return false; 224 | } 225 | } 226 | 227 | /** 228 | * 向一张hash表中放入数据,如果不存在将创建 229 | * 230 | * @param key 键 231 | * @param item 项 232 | * @param value 值 233 | * @return true 成功 false失败 234 | */ 235 | public boolean hset(String key, String item, Object value) { 236 | try { 237 | redisTemplate.opsForHash().put(key, item, value); 238 | return true; 239 | } catch (Exception e) { 240 | e.printStackTrace(); 241 | return false; 242 | } 243 | } 244 | 245 | /** 246 | * 向一张hash表中放入数据,如果不存在将创建 247 | * 248 | * @param key 键 249 | * @param item 项 250 | * @param value 值 251 | * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 252 | * @return true 成功 false失败 253 | */ 254 | public boolean hset(String key, String item, Object value, long time) { 255 | try { 256 | redisTemplate.opsForHash().put(key, item, value); 257 | if (time > 0) { 258 | expire(key, time); 259 | } 260 | return true; 261 | } catch (Exception e) { 262 | e.printStackTrace(); 263 | return false; 264 | } 265 | } 266 | 267 | /** 268 | * 删除hash表中的值 269 | * 270 | * @param key 键 不能为null 271 | * @param item 项 可以使多个 不能为null 272 | */ 273 | public void hdel(String key, Object... item) { 274 | redisTemplate.opsForHash().delete(key, item); 275 | } 276 | 277 | /** 278 | * 判断hash表中是否有该项的值 279 | * 280 | * @param key 键 不能为null 281 | * @param item 项 不能为null 282 | * @return true 存在 false不存在 283 | */ 284 | public boolean hHasKey(String key, String item) { 285 | return redisTemplate.opsForHash().hasKey(key, item); 286 | } 287 | 288 | /** 289 | * hash递增 如果不存在,就会创建一个 并把新增后的值返回 290 | * 291 | * @param key 键 292 | * @param item 项 293 | * @param by 要增加几(大于0) 294 | * @return 295 | */ 296 | public double hincr(String key, String item, double by) { 297 | return redisTemplate.opsForHash().increment(key, item, by); 298 | } 299 | 300 | /** 301 | * hash递减 302 | * 303 | * @param key 键 304 | * @param item 项 305 | * @param by 要减少记(小于0) 306 | * @return 307 | */ 308 | public double hdecr(String key, String item, double by) { 309 | return redisTemplate.opsForHash().increment(key, item, -by); 310 | } 311 | 312 | // ============================set============================= 313 | 314 | /** 315 | * 根据key获取Set中的所有值 316 | * 317 | * @param key 键 318 | * @return 319 | */ 320 | public Set sGet(String key) { 321 | try { 322 | return redisTemplate.opsForSet().members(key); 323 | } catch (Exception e) { 324 | e.printStackTrace(); 325 | return null; 326 | } 327 | } 328 | 329 | /** 330 | * 根据value从一个set中查询,是否存在 331 | * 332 | * @param key 键 333 | * @param value 值 334 | * @return true 存在 false不存在 335 | */ 336 | public boolean sHasKey(String key, Object value) { 337 | try { 338 | return redisTemplate.opsForSet().isMember(key, value); 339 | } catch (Exception e) { 340 | e.printStackTrace(); 341 | return false; 342 | } 343 | } 344 | 345 | /** 346 | * 将数据放入set缓存 347 | * 348 | * @param key 键 349 | * @param values 值 可以是多个 350 | * @return 成功个数 351 | */ 352 | public long sAdd(String key, Object... values) { 353 | try { 354 | return redisTemplate.opsForSet().add(key, values); 355 | } catch (Exception e) { 356 | e.printStackTrace(); 357 | return 0; 358 | } 359 | } 360 | 361 | /** 362 | * 将set数据放入缓存 363 | * 364 | * @param key 键 365 | * @param time 时间(秒) 366 | * @param values 值 可以是多个 367 | * @return 成功个数 368 | */ 369 | public long sSetAndTime(String key, long time, Object... values) { 370 | try { 371 | Long count = redisTemplate.opsForSet().add(key, values); 372 | if (time > 0) { 373 | expire(key, time); 374 | } 375 | return count; 376 | } catch (Exception e) { 377 | e.printStackTrace(); 378 | return 0; 379 | } 380 | } 381 | 382 | /** 383 | * 获取set缓存的长度 384 | * 385 | * @param key 键 386 | * @return 387 | */ 388 | public long sGetSetSize(String key) { 389 | try { 390 | return redisTemplate.opsForSet().size(key); 391 | } catch (Exception e) { 392 | e.printStackTrace(); 393 | return 0; 394 | } 395 | } 396 | 397 | /** 398 | * 移除值为value的 399 | * 400 | * @param key 键 401 | * @param values 值 可以是多个 402 | * @return 移除的个数 403 | */ 404 | public long setRemove(String key, Object... values) { 405 | try { 406 | Long count = redisTemplate.opsForSet().remove(key, values); 407 | return count; 408 | } catch (Exception e) { 409 | e.printStackTrace(); 410 | return 0; 411 | } 412 | } 413 | 414 | // ===============================list================================= 415 | 416 | /** 417 | * 获取list缓存的内容 418 | * 419 | * @param key 键 420 | * @param start 开始 421 | * @param end 结束 0 到 -1代表所有值 422 | * @return 423 | */ 424 | public List lGet(String key, long start, long end) { 425 | try { 426 | return redisTemplate.opsForList().range(key, start, end); 427 | } catch (Exception e) { 428 | e.printStackTrace(); 429 | return null; 430 | } 431 | } 432 | 433 | /** 434 | * 获取list缓存的长度 435 | * 436 | * @param key 键 437 | * @return 438 | */ 439 | public long lGetListSize(String key) { 440 | try { 441 | return redisTemplate.opsForList().size(key); 442 | } catch (Exception e) { 443 | e.printStackTrace(); 444 | return 0; 445 | } 446 | } 447 | 448 | /** 449 | * 通过索引 获取list中的值 450 | * 451 | * @param key 键 452 | * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 453 | * @return 454 | */ 455 | public Object lGetIndex(String key, long index) { 456 | try { 457 | return redisTemplate.opsForList().index(key, index); 458 | } catch (Exception e) { 459 | e.printStackTrace(); 460 | return null; 461 | } 462 | } 463 | 464 | /** 465 | * 将list放入缓存 466 | * 467 | * @param key 键 468 | * @param value 值 469 | * @return 470 | */ 471 | public boolean lSet(String key, Object value) { 472 | try { 473 | redisTemplate.opsForList().rightPush(key, value); 474 | return true; 475 | } catch (Exception e) { 476 | e.printStackTrace(); 477 | return false; 478 | } 479 | } 480 | 481 | /** 482 | * 将list放入缓存 483 | * 484 | * @param key 键 485 | * @param value 值 486 | * @param time 时间(秒) 487 | */ 488 | public boolean lSet(String key, Object value, long time) { 489 | try { 490 | redisTemplate.opsForList().rightPush(key, value); 491 | if (time > 0) { 492 | expire(key, time); 493 | } 494 | return true; 495 | } catch (Exception e) { 496 | e.printStackTrace(); 497 | return false; 498 | } 499 | } 500 | 501 | /** 502 | * 将list放入缓存 503 | * 504 | * @param key 键 505 | * @param value 值 506 | */ 507 | public boolean lSet(String key, List value) { 508 | try { 509 | redisTemplate.opsForList().rightPushAll(key, value); 510 | return true; 511 | } catch (Exception e) { 512 | e.printStackTrace(); 513 | return false; 514 | } 515 | } 516 | 517 | /** 518 | * 将list放入缓存 519 | * 520 | * @param key 键 521 | * @param value 值 522 | * @param time 时间(秒) 523 | * @return 524 | */ 525 | public boolean lSet(String key, List value, long time) { 526 | try { 527 | redisTemplate.opsForList().rightPushAll(key, value); 528 | if (time > 0) { 529 | expire(key, time); 530 | } 531 | return true; 532 | } catch (Exception e) { 533 | e.printStackTrace(); 534 | return false; 535 | } 536 | } 537 | 538 | /** 539 | * 根据索引修改list中的某条数据 540 | * 541 | * @param key 键 542 | * @param index 索引 543 | * @param value 值 544 | * @return 545 | */ 546 | public boolean lUpdateIndex(String key, long index, Object value) { 547 | try { 548 | redisTemplate.opsForList().set(key, index, value); 549 | return true; 550 | } catch (Exception e) { 551 | e.printStackTrace(); 552 | return false; 553 | } 554 | } 555 | 556 | /** 557 | * 移除N个值为value 558 | * 559 | * @param key 键 560 | * @param count 移除多少个 561 | * @param value 值 562 | * @return 移除的个数 563 | */ 564 | public long lRemove(String key, long count, Object value) { 565 | try { 566 | Long remove = redisTemplate.opsForList().remove(key, count, value); 567 | return remove; 568 | } catch (Exception e) { 569 | e.printStackTrace(); 570 | return 0; 571 | } 572 | } 573 | } 574 | --------------------------------------------------------------------------------