From f2c153e3acea21f34e2a04f7f3ac446708b157b4 Mon Sep 17 00:00:00 2001 From: H1d3rOne <260588178@qq.com> Date: Thu, 5 Mar 2026 23:03:21 +0800 Subject: [PATCH 1/8] =?UTF-8?q?Initial=20commit:=20=E6=B7=BB=E5=8A=A0READM?= =?UTF-8?q?E.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3745a67 --- /dev/null +++ b/README.md @@ -0,0 +1,273 @@ +

+ 排盘工具 - 升级版 +

+

+ + + + + + +

+

+ 在线地址: + + https://www.muxuange.cn/paipan + +

+

+ 版本:v2.2.0 | 运行环境:jdk8 +

+ +``` +公历2025年8月22日更新 + +此仓库唯一,开源免费、不售卖、仅供参考使用 +https://gitee.com/shan-dai/xuan-utils-pro +``` + +## **八字排盘** + +🔈八字排盘提供基础数据,如:八字、主星、地势、空亡、神煞等。 + +### 代码示例 + +```java +public class BaZiTest { + public static void main(String[] args) { + + // 1、基础设置(可选) + BaZiJiChuSetting baZiJiChuSetting = new BaZiJiChuSetting(); + baZiJiChuSetting.setSex(1); // 性别(0:女。1:男) + // 更多基础设置... + + // 2、神煞设置(可选) + BaZiShenShaSetting baZiShenShaSetting = new BaZiShenShaSetting(); + baZiShenShaSetting.setTaiJiGuiRen(0); // 太极贵人(0:显示。1:关闭) + // 更多神煞设置... + + // 3、干支留意设置(可选) + BaZiGanZhiLiuYiSetting baZiGanZhiLiuYiSetting = new BaZiGanZhiLiuYiSetting(); + baZiGanZhiLiuYiSetting.setTianGanXiangSheng(0); // 天干相生(0:显示。1:关闭) + // 更多干支留意设置... + + // 4、初始化 + BaZi baZi = new BaZi(baZiJiChuSetting); // 使用基础设置初始化 + baZi.baZiShenShaSetting(baZiShenShaSetting); // 神煞设置 + baZi.baZiGanZhiLiuYiSetting(baZiGanZhiLiuYiSetting); // 干支留意设置 + + // 5、输出结果 + System.out.println(baZi); + + } +} +``` + +输出结果(部分数据): + +``` +公历:2024年01月01日00时00分00秒 农历:二〇二三年冬月二十(早)子时 星期:周一 季节:仲冬 生肖:兔 星座:摩羯座 月相:更待 月将:丑 月将神:大吉 五不遇时:false 年干支主星:正印 月干支主星:比肩 日干支主星:元男 时干支主星:比肩 年支藏干:[乙] 月支藏干:[癸] 日支藏干:[癸] 时支藏干:[癸] 年干支副星:[劫财] 月干支副星:[正印] 日干支副星:[正印] 时干支副星:[正印] 年干支自坐:长生 月干支自坐:沐浴 日干支自坐:沐浴 时干支自坐:沐浴 年干支星运:帝旺 月干支星运:沐浴 日干支星运:沐浴 时干支星运:沐浴年干支神煞:[天乙贵人, 福星贵人, 文昌贵人, 天厨贵人, 德秀贵人, 羊刃] 月干支神煞:[太极贵人, 福星贵人, 德秀贵人, 将星, 桃花, 红鸾, 披麻] 日干支神煞:[太极贵人, 福星贵人, 德秀贵人, 阴注阳受, 冲天煞, 桃花, 红鸾, 天赦, 披麻] 时干支神煞:[太极贵人, 福星贵人, 德秀贵人, 阴注阳受, 冲天煞, 将星, 桃花, 红鸾, 披麻] +``` + +### 效果预览 + +![paipan](https://xuan-utils-pro.oss-cn-qingdao.aliyuncs.com/pic/440701d3-3fcd-455b-95fb-ef301a6f757c.png) + +========================================================= + +## **六爻排盘** + +🔈六爻排盘提供基础数据,如:八字、五行、空亡、世应、六亲等。 + +### 代码示例 + +```java +public class LiuYaoTest { + public static void main(String[] args) { + + // 1、基础设置(可选) + LiuYaoJiChuSetting liuYaoJiChuSetting = new LiuYaoJiChuSetting(); + baZiJiChuSetting.setSex(1); // 性别(0:女。1:男) + // 更多基础设置... + + // 2、神煞设置(可选) + LiuYaoShenShaSetting liuYaoShenShaSetting = new LiuYaoShenShaSetting(); + liuYaoShenShaSetting.setTaiJiGuiRen(0); // 太极贵人(0:显示。1:关闭) + // 更多神煞设置... + + // 3、初始化 + LiuYao liuYao = new LiuYao(liuYaoJiChuSetting); // 使用基础设置初始化 + liuYao.liuYaoShenShaSetting(liuYaoShenShaSetting); // 神煞设置 + + // 4、输出结果 + System.out.println(liuYao); + + } +} +``` + +输出结果(部分数据): + +``` +公历:2024年01月01日00时00分00秒 农历:二〇二三年冬月二十(早)子时 星期:周一 季节:仲冬 生肖:兔 星座:摩羯座 月相:更待 月将:丑 月将神:大吉 五不遇时:false 上卦:离(☲) 下卦:震(☳) 本卦:火雷噬嗑(䷔) 变卦:震为雷(䷲) 互卦:水山蹇(䷦) 错卦:水风井(䷯) 综卦:山火贲(䷕) 伏卦:巽为风(䷸) +``` + +### 效果预览 + +![paipan](https://xuan-utils-pro.oss-cn-qingdao.aliyuncs.com/pic/fcc3ae97-2e46-4907-8ad2-f8705785a77b.png) + +========================================================= + +## **大六壬排盘** + +🔈大六壬排盘提供基础数据,如:地盘、天盘、人盘、四课、三传等。 + +### 代码示例 + +```java +public class DaLiuRenTest { + public static void main(String[] args) { + + // 1、基础设置(可选) + DaLiuRenJiChuSetting daLiuRenJiChuSetting = new DaLiuRenJiChuSetting(); + daLiuRenJiChuSetting.setSex(1); // 性别(0:女。1:男) + // 更多基础设置... + + // 2、初始化 + DaLiuRen daLiuRen = new DaLiuRen(daLiuRenJiChuSetting); // 使用基础设置初始化 + + // 3、输出结果 + System.out.println(daLiuRen); + + } +} +``` + +输出结果(部分数据): + +``` +公历:2024年01月01日00时00分00秒 农历:二〇二三年冬月二十(早)子时 星期:周一 季节:仲冬 生肖:兔 星座:摩羯座 月相:更待 月将:丑 月将神:大吉 五不遇时:false 天地盘类型:进连茹盘 地盘:[寅, 卯, 辰, 巳, 午, 未, 申, 酉, 戌, 亥, 子, 丑] 天盘:[卯, 辰, 巳, 午, 未, 申, 酉, 戌, 亥, 子, 丑, 寅] 神盘:[勾陈, 六合, 朱雀, 螣蛇, 贵人, 天后, 太阴, 玄武, 太常, 白虎, 天空, 青龙] 四课:[[卯, 甲], [辰, 卯], [丑, 子], [寅, 丑]] 三传:[辰, 巳, 午] +``` + +### 效果预览 + +![paipan](https://xuan-utils-pro.oss-cn-qingdao.aliyuncs.com/pic/1a036a3d-7f05-494b-8bdb-ac7ec40c7e84.png) + +========================================================= + +## **奇门遁甲排盘** + +🔈奇门遁甲排盘提供基础数据,如:地盘、天盘、人盘、神盘、动应、克应等。 + +### 代码示例(转盘) + +```java +public class QiMenTest { + public static void main(String[] args) { + + // 1、基础设置(可选) + QiMenZhuanPanJiChuSetting qiMenZhuanPanJiChuSetting = new QiMenZhuanPanJiChuSetting(); + qiMenZhuanPanJiChuSetting.setSex(1); // 性别(0:女。1:男) + // 更多基础设置... + + // 2、初始化 + QiMenZhuanPan qiMen = new QiMenZhuanPan(qiMenZhuanPanJiChuSetting); // 使用基础设置初始化 + + // 3、输出结果 + System.out.println(qiMen); + + } +} +``` + +输出结果(部分数据): + +``` +公历:2024年01月01日00时00分00秒 农历:二〇二三年冬月二十(早)子时 星期:周一 季节:仲冬 生肖:兔 星座:摩羯座 月相:更待 月将:丑 月将神:大吉 五不遇时:false 符头:甲子 节气:冬至上元 局数:阳遁1局 旬首:甲子 值符:天蓬 值使:休门 天乙:天蓬 地乙:休门 太乙:休门 地盘:[戊, 己, 庚, 辛, 壬, 癸, 丁, 丙, 乙] 天盘:[天蓬, 芮禽, 天冲, 天辅, , 天心, 天柱, 天任, 天英] 人盘:[休门, 死门, 伤门, 杜门, , 开门, 惊门, 生门, 景门] 神盘:[值符, 玄武, 太阴, 六合, , 九天, 九地, 螣蛇, 白虎] 六仪击刑:[己击刑(坤二宫)] 奇仪入墓:[辛入墓(巽四宫)] +``` + +### 效果预览(转盘) + +![paipan](https://xuan-utils-pro.oss-cn-qingdao.aliyuncs.com/pic/7e0304f8-4da3-4023-8b9f-4a9b82ee061f.png) + +========================================================= + +## **梅花易数排盘** + +🔈梅花易数排盘提供基础数据,如:上卦、下卦、本卦、变卦、互卦等。 + +### 代码示例 + +```java +public class MeiHuaTest { + public static void main(String[] args) { + + // 1、基础设置(可选) + MeiHuaJiChuSetting meiHuaJiChuSetting = new MeiHuaJiChuSetting(); + meiHuaJiChuSetting.setSex(1); // 性别(0:女。1:男) + // 更多基础设置... + + // 2、初始化 + MeiHua meiHua = new MeiHua(meiHuaJiChuSetting); // 使用基础设置初始化 + + // 3、输出结果 + System.out.println(meiHua); + + } +} +``` + +输出结果(部分数据): + +``` +公历:2024年01月01日00时00分00秒 农历:二〇二三年冬月二十(早)子时 星期:周一 季节:仲冬 生肖:兔 星座:摩羯座 月相:更待 月将:丑 月将神:大吉 五不遇时:false 卦码:346 上卦:离(☲) 下卦:震(☳) 动爻:6 本卦:火雷噬嗑(䷔) 变卦:震为雷(䷲) 互卦:水山蹇(䷦) 错卦:水风井(䷯) 综卦:山火贲(䷕) +``` + +### 效果预览 + +![paipan](https://xuan-utils-pro.oss-cn-qingdao.aliyuncs.com/pic/8a53175a-50dc-40c4-894c-e1fb85773053.png) + +========================================================= + +## **紫微斗数排盘** + +🔈紫微斗数排盘提供基础数据,如:命宫、身宫、四化、诸星等。 + +### 代码示例 + +```java +public class ZiWeiTest { + public static void main(String[] args) { + + // 1、基础设置(可选) + ZiWeiJiChuSetting ziWeiJiChuSetting = new ZiWeiJiChuSetting(); + ziWeiJiChuSetting.setSex(1); // 性别(0:女。1:男) + // 更多基础设置... + + // 2、初始化 + ZiWei ziWei = new ZiWei(ziWeiJiChuSetting); // 使用基础设置初始化 + + // 3、输出结果 + System.out.println(ziWei); + + } +} +``` + +输出结果(部分数据): + +``` +公历:2024年01月01日00时00分00秒 农历:二〇二三年冬月二十(早)子时 星期:周一 季节:仲冬 生肖:兔 星座:摩羯座 月相:更待 月将:丑 月将神:大吉 五不遇时:false 五行局:金四局 化禄星宫位解读:化禄星落财帛宫,善于理财,财源滚滚。 化权星宫位解读:化权星落父母宫,父母强势,有矛盾。 化科星宫位解读:化科星落兄弟宫,兄弟有名声,助力多。 化忌星宫位解读:化忌星落命宫宫,不听劝,易焦虑,年少顺利。 +``` + +### 效果预览 + +![paipan](https://xuan-utils-pro.oss-cn-qingdao.aliyuncs.com/pic/225964a8-b34f-4a2d-b802-c43adc2d7860.png) + +========================================================= + +## **更多功能正在完善中...** + +

+ 温馨提示:代码仅供参考,不得违法违规使用,否则后果自负! +

\ No newline at end of file From a769cd67c4fc5d30eba5766ecc6ff654988cd49d Mon Sep 17 00:00:00 2001 From: H1d3rOne <260588178@qq.com> Date: Thu, 5 Mar 2026 23:03:34 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0.gitignore=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a73858a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +target/ +.idea/ +/idea/ +.DS_Store +*.class +*.ipr +*.iml +*.iws +*.log +*.cache +*.diff +*.patch +*.tmp \ No newline at end of file From 885732add965ba6046354dbc381453e2bfc8b6f7 Mon Sep 17 00:00:00 2001 From: H1d3rOne <260588178@qq.com> Date: Thu, 5 Mar 2026 23:03:38 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0LICENSE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1d422d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 善待 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From c0d64dcc85bd9f2a0f9fda28c3a133b2557fd0c1 Mon Sep 17 00:00:00 2001 From: H1d3rOne <260588178@qq.com> Date: Thu, 5 Mar 2026 23:04:05 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0pom.xml=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e4a719d --- /dev/null +++ b/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + xuan + xuan-utils-pro + 2.0-SNAPSHOT + + + + + 1.8 + 2.4.5 + 1.8 + 1.8 + + UTF-8 + UTF-8 + + 5.3.29 + 9.0.78 + 4.13.2 + 1.2.58 + 2.6 + 2.6 + 1.6 + 3.8.1 + 1.7.2 + 1.7.4 + 2.17.1 + + + + + + + org.springframework + spring-core + ${spring.core.version} + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.embed.core.version} + + + + junit + junit + ${junit.version} + + + + org.projectlombok + lombok + RELEASE + compile + + + + com.alibaba + fastjson + ${alibaba.fastjson.version} + + + + commons-io + commons-io + ${commons.io.version} + + + commons-lang + commons-lang + ${commons.lang.version} + + + commons-validator + commons-validator + ${commons.validator.version} + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + + + + org.lionsoul + ip2region + ${ip2region.versoin} + + + + cn.6tail + lunar + ${lunar.version} + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.databind.version} + + + + \ No newline at end of file From e4e3f0fdb38b050511b32db4155528d96e840b67 Mon Sep 17 00:00:00 2001 From: H1d3rOne Date: Tue, 24 Mar 2026 11:55:29 +0800 Subject: [PATCH 5/8] feat: add h5 account auth flow --- .../java/xuan/controller/UserController.java | 286 +++++++++++++++++- src/main/java/xuan/entity/UserInfo.java | 12 + .../xuan/repository/UserInfoRepository.java | 1 + .../java/xuan/service/UserInfoService.java | 63 ++++ src/main/resources/application.properties | 11 +- xuan-deduct-uniapp/index.html | 12 + .../src/components/auth-modal/auth-modal.vue | 235 +++++++++++++- xuan-deduct-uniapp/src/index.html | 17 -- xuan-deduct-uniapp/src/pages.json | 8 + .../src/pages/auth/wechat-callback.vue | 131 ++++++++ xuan-deduct-uniapp/src/utils/api.js | 18 ++ xuan-deduct-uniapp/src/utils/authCheck.js | 25 ++ xuan-deduct-uniapp/vite.config.ts | 6 +- 13 files changed, 774 insertions(+), 51 deletions(-) create mode 100644 xuan-deduct-uniapp/index.html delete mode 100644 xuan-deduct-uniapp/src/index.html create mode 100644 xuan-deduct-uniapp/src/pages/auth/wechat-callback.vue diff --git a/src/main/java/xuan/controller/UserController.java b/src/main/java/xuan/controller/UserController.java index d5ed3ea..fac7e01 100644 --- a/src/main/java/xuan/controller/UserController.java +++ b/src/main/java/xuan/controller/UserController.java @@ -2,20 +2,29 @@ package xuan.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.multipart.MultipartFile; import xuan.entity.UserInfo; import xuan.service.UserInfoService; import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; +import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping("/api/user") @@ -30,6 +39,15 @@ public class UserController { @Value("${wechat.secret:}") private String appSecret; + + @Value("${wechat.web.appid:}") + private String webAppId; + + @Value("${wechat.web.secret:}") + private String webAppSecret; + + @Value("${wechat.web.callback-base-url:}") + private String webCallbackBaseUrl; @PostMapping("/login") public Map login(@RequestBody Map params) { @@ -90,23 +108,10 @@ public class UserController { } UserInfo user = userInfoService.loginOrRegister(openId, username, avatarUrl); - System.out.println("登录成功: userId=" + user.getUserId() + ", openId=" + user.getOpenId()); - - Map data = new HashMap<>(); - data.put("id", user.getId()); - data.put("userId", user.getUserId()); - data.put("openId", user.getOpenId()); - data.put("avatarUrl", user.getAvatarUrl()); - data.put("username", user.getUsername()); - data.put("phone", user.getPhone()); - data.put("vipLevel", user.getVipLevel()); - data.put("coins", user.getCoins()); - data.put("streakDays", user.getStreakDays()); - data.put("createTime", user.getCreateTime()); - + result.put("success", true); - result.put("data", data); + result.put("data", buildUserData(user)); } catch (Exception e) { System.err.println("登录异常: " + e.getMessage()); e.printStackTrace(); @@ -115,6 +120,117 @@ public class UserController { } return result; } + + @GetMapping("/wechat/h5/start") + public ResponseEntity startWechatH5Login( + @RequestParam(required = false) String returnUrl, + HttpServletRequest request + ) { + String targetReturnUrl = StringUtils.hasText(returnUrl) + ? returnUrl + : resolveDefaultFrontendReturnUrl(request); + + if (!StringUtils.hasText(webAppId) || !StringUtils.hasText(webAppSecret)) { + return redirect(buildFrontendRedirectUrl(targetReturnUrl, false, "未配置微信网站应用登录")); + } + + String callbackBaseUrl = StringUtils.hasText(webCallbackBaseUrl) + ? webCallbackBaseUrl + : resolveRequestBaseUrl(request); + + String callbackUrl = UriComponentsBuilder + .fromHttpUrl(callbackBaseUrl + "/api/user/wechat/h5/callback") + .queryParam("returnUrl", targetReturnUrl) + .build() + .encode() + .toUriString(); + + String wechatLoginUrl = UriComponentsBuilder + .fromHttpUrl("https://open.weixin.qq.com/connect/qrconnect") + .queryParam("appid", webAppId) + .queryParam("redirect_uri", callbackUrl) + .queryParam("response_type", "code") + .queryParam("scope", "snsapi_login") + .queryParam("state", "xuan_h5_login") + .build() + .encode() + .toUriString() + "#wechat_redirect"; + + return redirect(wechatLoginUrl); + } + + @GetMapping("/wechat/h5/callback") + public ResponseEntity handleWechatH5Callback( + @RequestParam(required = false) String code, + @RequestParam(required = false) String returnUrl + ) { + String targetReturnUrl = StringUtils.hasText(returnUrl) + ? returnUrl + : "/"; + + if (!StringUtils.hasText(webAppId) || !StringUtils.hasText(webAppSecret)) { + return redirect(buildFrontendRedirectUrl(targetReturnUrl, false, "未配置微信网站应用登录")); + } + + if (!StringUtils.hasText(code)) { + return redirect(buildFrontendRedirectUrl(targetReturnUrl, false, "未获取到微信登录凭证")); + } + + try { + RestTemplate restTemplate = new RestTemplate(); + String accessTokenUrl = UriComponentsBuilder + .fromHttpUrl("https://api.weixin.qq.com/sns/oauth2/access_token") + .queryParam("appid", webAppId) + .queryParam("secret", webAppSecret) + .queryParam("code", code) + .queryParam("grant_type", "authorization_code") + .build() + .encode() + .toUriString(); + + @SuppressWarnings("unchecked") + Map tokenResult = restTemplate.getForObject(accessTokenUrl, Map.class); + if (tokenResult == null || !tokenResult.containsKey("openid") || !tokenResult.containsKey("access_token")) { + return redirect(buildFrontendRedirectUrl(targetReturnUrl, false, extractWechatError(tokenResult, "微信扫码登录失败"))); + } + + String openId = String.valueOf(tokenResult.get("openid")); + String accessToken = String.valueOf(tokenResult.get("access_token")); + + String username = "微信用户"; + String avatarUrl = ""; + String userInfoUrl = UriComponentsBuilder + .fromHttpUrl("https://api.weixin.qq.com/sns/userinfo") + .queryParam("access_token", accessToken) + .queryParam("openid", openId) + .queryParam("lang", "zh_CN") + .build() + .encode() + .toUriString(); + + try { + @SuppressWarnings("unchecked") + Map userInfoResult = restTemplate.getForObject(userInfoUrl, Map.class); + if (userInfoResult != null && !userInfoResult.containsKey("errcode")) { + if (userInfoResult.get("nickname") != null) { + username = String.valueOf(userInfoResult.get("nickname")); + } + if (userInfoResult.get("headimgurl") != null) { + avatarUrl = String.valueOf(userInfoResult.get("headimgurl")); + } + } + } catch (Exception e) { + System.err.println("获取微信用户信息失败: " + e.getMessage()); + } + + UserInfo user = userInfoService.loginOrRegister(openId, username, avatarUrl); + return redirect(buildFrontendRedirectUrl(targetReturnUrl, true, null, buildUserData(user))); + } catch (Exception e) { + System.err.println("H5微信扫码回调异常: " + e.getMessage()); + e.printStackTrace(); + return redirect(buildFrontendRedirectUrl(targetReturnUrl, false, "微信扫码登录失败,请重试")); + } + } @GetMapping("/info") public Map getUserInfo(@RequestParam String userId) { @@ -146,6 +262,41 @@ public class UserController { } return result; } + + @PostMapping("/h5/register") + public Map registerH5(@RequestBody Map params) { + Map result = new HashMap<>(); + try { + String loginName = params.get("loginName") != null ? params.get("loginName").toString() : ""; + String nickname = params.get("nickname") != null ? params.get("nickname").toString() : ""; + String password = params.get("password") != null ? params.get("password").toString() : ""; + + UserInfo user = userInfoService.registerWithPassword(loginName, nickname, password); + result.put("success", true); + result.put("data", buildUserData(user)); + } catch (Exception e) { + result.put("success", false); + result.put("error", e.getMessage()); + } + return result; + } + + @PostMapping("/h5/login") + public Map loginH5(@RequestBody Map params) { + Map result = new HashMap<>(); + try { + String loginName = params.get("loginName") != null ? params.get("loginName").toString() : ""; + String password = params.get("password") != null ? params.get("password").toString() : ""; + + UserInfo user = userInfoService.loginWithPassword(loginName, password); + result.put("success", true); + result.put("data", buildUserData(user)); + } catch (Exception e) { + result.put("success", false); + result.put("error", e.getMessage()); + } + return result; + } @PostMapping("/updateVip") public Map updateVipLevel(@RequestBody Map params) { @@ -407,4 +558,109 @@ public class UserController { } return result; } + + private Map buildUserData(UserInfo user) { + Map data = new HashMap<>(); + data.put("id", user.getId()); + data.put("userId", user.getUserId()); + data.put("openId", user.getOpenId()); + data.put("avatarUrl", user.getAvatarUrl()); + data.put("username", user.getUsername()); + data.put("loginName", user.getLoginName()); + data.put("phone", user.getPhone()); + data.put("vipLevel", user.getVipLevel()); + data.put("coins", user.getCoins()); + data.put("streakDays", user.getStreakDays()); + data.put("createTime", user.getCreateTime()); + return data; + } + + private ResponseEntity redirect(String targetUrl) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(targetUrl)); + return new ResponseEntity<>(headers, HttpStatus.FOUND); + } + + private String buildFrontendRedirectUrl(String returnUrl, boolean success, String errorMessage) { + return buildFrontendRedirectUrl(returnUrl, success, errorMessage, null); + } + + private String buildFrontendRedirectUrl( + String returnUrl, + boolean success, + String errorMessage, + Map payload + ) { + Map params = new LinkedHashMap<>(); + params.put("success", success ? "1" : "0"); + if (!success && StringUtils.hasText(errorMessage)) { + params.put("error", errorMessage); + } + if (payload != null) { + params.putAll(payload); + } + return appendParamsToUrl(returnUrl, params); + } + + private String appendParamsToUrl(String url, Map params) { + if (!StringUtils.hasText(url)) { + return "/"; + } + + int hashIndex = url.indexOf('#'); + if (hashIndex >= 0) { + String prefix = url.substring(0, hashIndex); + String fragment = url.substring(hashIndex + 1); + String separator = fragment.contains("?") ? "&" : "?"; + return prefix + "#" + fragment + separator + toQueryString(params); + } + + String separator = url.contains("?") ? "&" : "?"; + return url + separator + toQueryString(params); + } + + private String toQueryString(Map params) { + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (entry.getValue() == null) { + continue; + } + if (builder.length() > 0) { + builder.append("&"); + } + builder + .append(urlEncode(entry.getKey())) + .append("=") + .append(urlEncode(String.valueOf(entry.getValue()))); + } + return builder.toString(); + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(value, "UTF-8"); + } catch (Exception e) { + throw new IllegalStateException("URL编码失败", e); + } + } + + private String extractWechatError(Map wxResult, String fallback) { + if (wxResult == null) { + return fallback; + } + Object errMsg = wxResult.get("errmsg"); + if (errMsg != null && StringUtils.hasText(String.valueOf(errMsg))) { + return String.valueOf(errMsg); + } + return fallback; + } + + private String resolveRequestBaseUrl(HttpServletRequest request) { + return request.getScheme() + "://" + request.getServerName() + + (request.getServerPort() == 80 || request.getServerPort() == 443 ? "" : ":" + request.getServerPort()); + } + + private String resolveDefaultFrontendReturnUrl(HttpServletRequest request) { + return resolveRequestBaseUrl(request) + "/#/pages/auth/wechat-callback"; + } } diff --git a/src/main/java/xuan/entity/UserInfo.java b/src/main/java/xuan/entity/UserInfo.java index 0ae41c4..747a93f 100644 --- a/src/main/java/xuan/entity/UserInfo.java +++ b/src/main/java/xuan/entity/UserInfo.java @@ -19,6 +19,12 @@ public class UserInfo { @Column(length = 50) private String username; + + @Column(unique = true, length = 50) + private String loginName; + + @Column(length = 64) + private String passwordHash; @Column(length = 20) private String userId; @@ -65,6 +71,12 @@ public class UserInfo { public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } + + public String getLoginName() { return loginName; } + public void setLoginName(String loginName) { this.loginName = loginName; } + + public String getPasswordHash() { return passwordHash; } + public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/src/main/java/xuan/repository/UserInfoRepository.java b/src/main/java/xuan/repository/UserInfoRepository.java index c53ad18..843d841 100644 --- a/src/main/java/xuan/repository/UserInfoRepository.java +++ b/src/main/java/xuan/repository/UserInfoRepository.java @@ -8,4 +8,5 @@ import xuan.entity.UserInfo; public interface UserInfoRepository extends JpaRepository { UserInfo findByOpenId(String openId); UserInfo findByUserId(String userId); + UserInfo findByLoginName(String loginName); } diff --git a/src/main/java/xuan/service/UserInfoService.java b/src/main/java/xuan/service/UserInfoService.java index 3c7dd5f..f3b16eb 100644 --- a/src/main/java/xuan/service/UserInfoService.java +++ b/src/main/java/xuan/service/UserInfoService.java @@ -8,6 +8,8 @@ import xuan.entity.CoinConsumeRecord; import xuan.repository.UserInfoRepository; import xuan.repository.CoinConsumeRecordRepository; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.util.Random; @Service @@ -40,6 +42,53 @@ public class UserInfoService { return userInfoRepository.save(user); } + + public UserInfo registerWithPassword(String loginName, String nickname, String password) { + if (loginName == null || loginName.trim().isEmpty()) { + throw new IllegalArgumentException("请输入用户名"); + } + if (nickname == null || nickname.trim().isEmpty()) { + throw new IllegalArgumentException("请输入昵称"); + } + if (password == null || password.length() < 6) { + throw new IllegalArgumentException("密码至少6位"); + } + + String normalizedLoginName = loginName.trim(); + if (userInfoRepository.findByLoginName(normalizedLoginName) != null) { + throw new IllegalArgumentException("用户名已存在"); + } + + UserInfo user = new UserInfo(); + user.setOpenId("h5_" + normalizedLoginName); + user.setLoginName(normalizedLoginName); + user.setPasswordHash(hashPassword(password)); + user.setUsername(nickname.trim()); + user.setAvatarUrl(""); + user.setUserId(generateUserId()); + user.setVipLevel(0); + user.setStreakDays(0); + + return userInfoRepository.save(user); + } + + public UserInfo loginWithPassword(String loginName, String password) { + if (loginName == null || loginName.trim().isEmpty()) { + throw new IllegalArgumentException("请输入用户名"); + } + if (password == null || password.isEmpty()) { + throw new IllegalArgumentException("请输入密码"); + } + + UserInfo user = userInfoRepository.findByLoginName(loginName.trim()); + if (user == null || user.getPasswordHash() == null) { + throw new IllegalArgumentException("用户名或密码错误"); + } + if (!user.getPasswordHash().equals(hashPassword(password))) { + throw new IllegalArgumentException("用户名或密码错误"); + } + return user; + } public UserInfo getUserInfo(String userId) { return userInfoRepository.findByUserId(userId); @@ -175,4 +224,18 @@ public class UserInfoService { } return sb.toString(); } + + private String hashPassword(String password) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] bytes = digest.digest(password.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (Exception e) { + throw new IllegalStateException("密码处理失败", e); + } + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 606335f..70f8d81 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,7 +9,7 @@ spring.datasource.password=xuan_password # JPA Configuration spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true @@ -17,5 +17,10 @@ spring.jpa.properties.hibernate.format_sql=true logging.level.xuan=DEBUG # WeChat Mini Program Configuration -wechat.appid=wxbae101ca6e1f61e6 -wechat.secret=26f414d93627acf3e1d895231296b313 +wechat.appid=${WECHAT_APPID:} +wechat.secret=${WECHAT_SECRET:} + +# WeChat H5 QR Login Configuration +wechat.web.appid=${WECHAT_WEB_APPID:${WECHAT_APPID:}} +wechat.web.secret=${WECHAT_WEB_SECRET:${WECHAT_SECRET:}} +wechat.web.callback-base-url=${WECHAT_WEB_CALLBACK_BASE_URL:https://api.bemover.xyz} diff --git a/xuan-deduct-uniapp/index.html b/xuan-deduct-uniapp/index.html new file mode 100644 index 0000000..a5cf36a --- /dev/null +++ b/xuan-deduct-uniapp/index.html @@ -0,0 +1,12 @@ + + + + + + 排盘工具 + + +
+ + + diff --git a/xuan-deduct-uniapp/src/components/auth-modal/auth-modal.vue b/xuan-deduct-uniapp/src/components/auth-modal/auth-modal.vue index 0d84126..91dba80 100644 --- a/xuan-deduct-uniapp/src/components/auth-modal/auth-modal.vue +++ b/xuan-deduct-uniapp/src/components/auth-modal/auth-modal.vue @@ -18,13 +18,74 @@ 🔐 - 微信授权登录 + {{ isH5 ? '账号登录' : '微信授权登录' }} {{ getStatusText() }} + + + + 注册 + + + 登录 + + + + + + + + + + {{ h5Mode === 'register' ? '注册后将自动登录,密码至少 6 位。' : '输入用户名和密码登录已有账号。' }} + + + + + + + + - + 1 @@ -98,6 +159,7 @@ - - - -
- - - diff --git a/xuan-deduct-uniapp/src/pages.json b/xuan-deduct-uniapp/src/pages.json index 6739796..53575d8 100644 --- a/xuan-deduct-uniapp/src/pages.json +++ b/xuan-deduct-uniapp/src/pages.json @@ -281,6 +281,14 @@ "navigationBarTextStyle": "white" } }, + { + "path": "pages/auth/wechat-callback", + "style": { + "navigationBarTitleText": "微信登录中", + "navigationBarBackgroundColor": "#07C160", + "navigationBarTextStyle": "white" + } + }, { "path": "pages/share/view", "style": { diff --git a/xuan-deduct-uniapp/src/pages/auth/wechat-callback.vue b/xuan-deduct-uniapp/src/pages/auth/wechat-callback.vue new file mode 100644 index 0000000..784e3c0 --- /dev/null +++ b/xuan-deduct-uniapp/src/pages/auth/wechat-callback.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/xuan-deduct-uniapp/src/utils/api.js b/xuan-deduct-uniapp/src/utils/api.js index 72181c4..8039f37 100644 --- a/xuan-deduct-uniapp/src/utils/api.js +++ b/xuan-deduct-uniapp/src/utils/api.js @@ -429,6 +429,24 @@ const userApi = { description: description } }) + }, + getWechatH5LoginUrl: (returnUrl) => { + const query = returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : '' + return `${BASE_URL}/user/wechat/h5/start${query}` + }, + registerH5: (data) => { + return request({ + url: '/user/h5/register', + method: 'POST', + data + }) + }, + loginH5: (data) => { + return request({ + url: '/user/h5/login', + method: 'POST', + data + }) } } diff --git a/xuan-deduct-uniapp/src/utils/authCheck.js b/xuan-deduct-uniapp/src/utils/authCheck.js index e153fa3..1de3f20 100644 --- a/xuan-deduct-uniapp/src/utils/authCheck.js +++ b/xuan-deduct-uniapp/src/utils/authCheck.js @@ -68,6 +68,31 @@ const authCheck = { const userInfo = this.getUserInfo() return userInfo ? userInfo.userId : null }, + + saveLogin(userData, fallbackUsername, baseUrl) { + if (!userData) { + return + } + + uni.setStorageSync('userId', userData.userId) + uni.setStorageSync('userInfo', JSON.stringify(userData)) + uni.setStorageSync('userCoins', userData.coins || 0) + uni.setStorageSync('vipLevel', userData.vipLevel || 0) + uni.setStorageSync('username', userData.username || fallbackUsername || '微信用户') + + let avatarUrl = userData.avatarUrl || '' + if ( + baseUrl && + avatarUrl && + !avatarUrl.startsWith('http') && + !avatarUrl.startsWith('wxfile://') + ) { + avatarUrl = baseUrl.replace('/api', '') + avatarUrl + } + + uni.setStorageSync('userAvatar', avatarUrl) + uni.setStorageSync('isLoggedIn', true) + }, logout() { uni.removeStorageSync('isLoggedIn') diff --git a/xuan-deduct-uniapp/vite.config.ts b/xuan-deduct-uniapp/vite.config.ts index 428716b..d329853 100644 --- a/xuan-deduct-uniapp/vite.config.ts +++ b/xuan-deduct-uniapp/vite.config.ts @@ -3,5 +3,9 @@ import uni from '@dcloudio/vite-plugin-uni' export default defineConfig({ plugins: [uni()], - root: 'src' + server: { + host: '0.0.0.0', + port: 5173, + strictPort: true + } }) From 2c35491370b321155d3b38c6b07c9655e4fb32c9 Mon Sep 17 00:00:00 2001 From: H1d3rOne Date: Thu, 7 May 2026 03:58:44 +0800 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20AI=E6=9C=8D=E5=8A=A1=E9=87=8D?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E6=94=AF=E6=8C=81OpenAI=20compatible=20provi?= =?UTF-8?q?der=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + src/main/java/xuan/config/AiProperties.java | 58 ++++ .../java/xuan/controller/BaZiController.java | 5 +- .../xuan/controller/DaLiuRenController.java | 6 +- .../xuan/controller/LiuYaoController.java | 6 +- .../xuan/controller/MeiHuaController.java | 6 +- .../xuan/controller/NamingController.java | 6 +- .../java/xuan/controller/QiMenController.java | 6 +- .../java/xuan/controller/TaLuoController.java | 5 +- .../xuan/controller/ZhanXingController.java | 6 +- .../java/xuan/controller/ZiWeiController.java | 6 +- src/main/java/xuan/service/AiService.java | 272 ++++++++++++++++++ .../java/xuan/service/HunYuanAiService.java | 186 ------------ src/main/resources/application.properties | 18 ++ .../resources/application.properties.template | 44 +++ 15 files changed, 428 insertions(+), 204 deletions(-) create mode 100644 src/main/java/xuan/config/AiProperties.java create mode 100644 src/main/java/xuan/service/AiService.java delete mode 100644 src/main/java/xuan/service/HunYuanAiService.java create mode 100644 src/main/resources/application.properties.template diff --git a/.gitignore b/.gitignore index 086475c..e129733 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ xuan-deduct-uniapp/unpackage/ xuan-deduct-uniapp/.hbuilderx/ .trae/ + +src/main/resources/application.properties diff --git a/src/main/java/xuan/config/AiProperties.java b/src/main/java/xuan/config/AiProperties.java new file mode 100644 index 0000000..48900b9 --- /dev/null +++ b/src/main/java/xuan/config/AiProperties.java @@ -0,0 +1,58 @@ +package xuan.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "ai") +public class AiProperties { + + private String provider = "openai-compatible"; + private String apiKey = ""; + private String baseUrl = "https://api.openai.com"; + private String model = "gpt-3.5-turbo"; + private double temperature = 0.7; + private double topP = 0.8; + private int maxTokens = 2048; + private int connectTimeout = 60; + private int readTimeout = 120; + + private TencentHunyuan tencentHunyuan = new TencentHunyuan(); + + public static class TencentHunyuan { + private String secretId = ""; + private String secretKey = ""; + private String region = "ap-guangzhou"; + private String model = "hunyuan-lite"; + + public String getSecretId() { return secretId; } + public void setSecretId(String secretId) { this.secretId = secretId; } + public String getSecretKey() { return secretKey; } + public void setSecretKey(String secretKey) { this.secretKey = secretKey; } + public String getRegion() { return region; } + public void setRegion(String region) { this.region = region; } + public String getModel() { return model; } + public void setModel(String model) { this.model = model; } + } + + public String getProvider() { return provider; } + public void setProvider(String provider) { this.provider = provider; } + public String getApiKey() { return apiKey; } + public void setApiKey(String apiKey) { this.apiKey = apiKey; } + public String getBaseUrl() { return baseUrl; } + public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + public String getModel() { return model; } + public void setModel(String model) { this.model = model; } + public double getTemperature() { return temperature; } + public void setTemperature(double temperature) { this.temperature = temperature; } + public double getTopP() { return topP; } + public void setTopP(double topP) { this.topP = topP; } + public int getMaxTokens() { return maxTokens; } + public void setMaxTokens(int maxTokens) { this.maxTokens = maxTokens; } + public int getConnectTimeout() { return connectTimeout; } + public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; } + public int getReadTimeout() { return readTimeout; } + public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } + public TencentHunyuan getTencentHunyuan() { return tencentHunyuan; } + public void setTencentHunyuan(TencentHunyuan tencentHunyuan) { this.tencentHunyuan = tencentHunyuan; } +} diff --git a/src/main/java/xuan/controller/BaZiController.java b/src/main/java/xuan/controller/BaZiController.java index 452632b..d71f484 100644 --- a/src/main/java/xuan/controller/BaZiController.java +++ b/src/main/java/xuan/controller/BaZiController.java @@ -8,7 +8,7 @@ import xuan.core.bazi.settings.BaZiJiChuSetting; import xuan.core.bazi.settings.BaZiShenShaSetting; import xuan.core.bazi.settings.BaZiGanZhiLiuYiSetting; import xuan.entity.DeductRecord; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import xuan.service.DeductRecordService; @@ -19,7 +19,8 @@ import java.util.Map; @RequestMapping("/api/bazi") public class BaZiController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @Autowired private DeductRecordService recordService; diff --git a/src/main/java/xuan/controller/DaLiuRenController.java b/src/main/java/xuan/controller/DaLiuRenController.java index fe2c89e..0a76f8b 100644 --- a/src/main/java/xuan/controller/DaLiuRenController.java +++ b/src/main/java/xuan/controller/DaLiuRenController.java @@ -1,10 +1,11 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.daliuren.DaLiuRen; import xuan.core.daliuren.settings.DaLiuRenJiChuSetting; import xuan.core.daliuren.settings.DaLiuRenShenShaSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -13,7 +14,8 @@ import java.util.*; @RequestMapping("/api/daliuren") public class DaLiuRenController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody DaLiuRenRequest request) { diff --git a/src/main/java/xuan/controller/LiuYaoController.java b/src/main/java/xuan/controller/LiuYaoController.java index ee1e0c2..79a9279 100644 --- a/src/main/java/xuan/controller/LiuYaoController.java +++ b/src/main/java/xuan/controller/LiuYaoController.java @@ -1,10 +1,11 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.liuyao.LiuYao; import xuan.core.liuyao.settings.LiuYaoJiChuSetting; import xuan.core.liuyao.settings.LiuYaoShenShaSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.HashMap; @@ -14,7 +15,8 @@ import java.util.Map; @RequestMapping("/api/liuyao") public class LiuYaoController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody LiuYaoRequest request) { diff --git a/src/main/java/xuan/controller/MeiHuaController.java b/src/main/java/xuan/controller/MeiHuaController.java index 670e29a..c751b8c 100644 --- a/src/main/java/xuan/controller/MeiHuaController.java +++ b/src/main/java/xuan/controller/MeiHuaController.java @@ -1,9 +1,10 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.meihua.MeiHua; import xuan.core.meihua.settings.MeiHuaJiChuSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.HashMap; @@ -13,7 +14,8 @@ import java.util.Map; @RequestMapping("/api/meihua") public class MeiHuaController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody MeiHuaRequest request) { diff --git a/src/main/java/xuan/controller/NamingController.java b/src/main/java/xuan/controller/NamingController.java index 1c84393..cd52d08 100644 --- a/src/main/java/xuan/controller/NamingController.java +++ b/src/main/java/xuan/controller/NamingController.java @@ -1,7 +1,8 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -10,7 +11,8 @@ import java.util.*; @RequestMapping("/api/naming") public class NamingController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/generate") public Map generate(@RequestBody NamingRequest request) { diff --git a/src/main/java/xuan/controller/QiMenController.java b/src/main/java/xuan/controller/QiMenController.java index 917a062..76558c0 100644 --- a/src/main/java/xuan/controller/QiMenController.java +++ b/src/main/java/xuan/controller/QiMenController.java @@ -1,9 +1,10 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.qimen.zhuan.QiMenZhuanPan; import xuan.core.qimen.zhuan.settings.QiMenZhuanPanJiChuSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.HashMap; @@ -13,7 +14,8 @@ import java.util.Map; @RequestMapping("/api/qimen") public class QiMenController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody QiMenRequest request) { diff --git a/src/main/java/xuan/controller/TaLuoController.java b/src/main/java/xuan/controller/TaLuoController.java index 64beca0..cb998ba 100644 --- a/src/main/java/xuan/controller/TaLuoController.java +++ b/src/main/java/xuan/controller/TaLuoController.java @@ -2,7 +2,7 @@ package xuan.controller; import org.springframework.web.bind.annotation.*; import org.springframework.beans.factory.annotation.Autowired; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import xuan.service.DeductRecordService; import xuan.entity.DeductRecord; @@ -14,7 +14,8 @@ import java.util.*; @RequestMapping("/api/taluo") public class TaLuoController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @Autowired private DeductRecordService recordService; diff --git a/src/main/java/xuan/controller/ZhanXingController.java b/src/main/java/xuan/controller/ZhanXingController.java index 61627bd..0a1b024 100644 --- a/src/main/java/xuan/controller/ZhanXingController.java +++ b/src/main/java/xuan/controller/ZhanXingController.java @@ -1,7 +1,8 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -10,7 +11,8 @@ import java.util.*; @RequestMapping("/api/zhanxing") public class ZhanXingController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody ZhanXingRequest request) { diff --git a/src/main/java/xuan/controller/ZiWeiController.java b/src/main/java/xuan/controller/ZiWeiController.java index 0022126..d6b066a 100644 --- a/src/main/java/xuan/controller/ZiWeiController.java +++ b/src/main/java/xuan/controller/ZiWeiController.java @@ -1,9 +1,10 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.ziwei.ZiWei; import xuan.core.ziwei.settings.ZiWeiJiChuSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -12,7 +13,8 @@ import java.util.*; @RequestMapping("/api/ziwei") public class ZiWeiController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody ZiWeiRequest request) { diff --git a/src/main/java/xuan/service/AiService.java b/src/main/java/xuan/service/AiService.java new file mode 100644 index 0000000..95318be --- /dev/null +++ b/src/main/java/xuan/service/AiService.java @@ -0,0 +1,272 @@ +package xuan.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import okhttp3.*; +import org.springframework.stereotype.Service; +import xuan.config.AiProperties; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Service +public class AiService { + + private final AiProperties properties; + private final OkHttpClient client; + + public AiService(AiProperties properties) { + this.properties = properties; + this.client = new OkHttpClient.Builder() + .connectTimeout(properties.getConnectTimeout(), TimeUnit.SECONDS) + .readTimeout(properties.getReadTimeout(), TimeUnit.SECONDS) + .writeTimeout(properties.getConnectTimeout(), TimeUnit.SECONDS) + .build(); + } + + public String chat(String systemPrompt, String userMessage) { + String provider = properties.getProvider(); + if ("tencent-hunyuan".equals(provider)) { + return chatTencentHunyuan(systemPrompt, userMessage); + } + return chatOpenAICompatible(systemPrompt, userMessage); + } + + private String chatOpenAICompatible(String systemPrompt, String userMessage) { + String apiKey = properties.getApiKey(); + if (apiKey == null || apiKey.isEmpty()) { + return "AI服务未配置,请在application.properties中设置ai.api-key。"; + } + + try { + String baseUrl = properties.getBaseUrl().replaceAll("/+$", ""); + String url = baseUrl + "/v1/chat/completions"; + + JSONObject requestBody = new JSONObject(); + requestBody.put("model", properties.getModel()); + + JSONArray messages = new JSONArray(); + JSONObject systemMsg = new JSONObject(); + systemMsg.put("role", "system"); + systemMsg.put("content", systemPrompt); + messages.add(systemMsg); + + JSONObject userMsg = new JSONObject(); + userMsg.put("role", "user"); + userMsg.put("content", userMessage); + messages.add(userMsg); + + requestBody.put("messages", messages); + requestBody.put("temperature", properties.getTemperature()); + requestBody.put("top_p", properties.getTopP()); + requestBody.put("max_tokens", properties.getMaxTokens()); + + String payload = requestBody.toJSONString(); + + Request request = new Request.Builder() + .url(url) + .post(RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"))) + .addHeader("Authorization", "Bearer " + apiKey) + .addHeader("Content-Type", "application/json; charset=utf-8") + .build(); + + try (Response response = client.newCall(request).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + if (!response.isSuccessful()) { + return "AI服务请求失败(HTTP " + response.code() + "):" + responseBody; + } + return parseOpenAIResponse(responseBody); + } + } catch (Exception e) { + e.printStackTrace(); + return "AI分析暂时不可用,请稍后再试。错误信息:" + e.getMessage(); + } + } + + private String parseOpenAIResponse(String responseBody) { + try { + JSONObject json = JSON.parseObject(responseBody); + JSONArray choices = json.getJSONArray("choices"); + if (choices != null && !choices.isEmpty()) { + JSONObject choice = choices.getJSONObject(0); + JSONObject message = choice.getJSONObject("message"); + if (message != null) { + return message.getString("content"); + } + } + return "AI未返回有效内容"; + } catch (Exception e) { + return "解析AI响应失败:" + e.getMessage(); + } + } + + private String chatTencentHunyuan(String systemPrompt, String userMessage) { + AiProperties.TencentHunyuan hy = properties.getTencentHunyuan(); + String secretId = hy.getSecretId(); + String secretKey = hy.getSecretKey(); + if (secretId == null || secretId.isEmpty() || secretKey == null || secretKey.isEmpty()) { + return "腾讯混元AI服务未配置,请设置ai.tencent-hunyuan.secret-id和ai.tencent-hunyuan.secret-key。"; + } + + try { + String response = sendTencentHunyuanRequest(systemPrompt, userMessage, secretId, secretKey, hy); + return parseTencentHunyuanResponse(response); + } catch (Exception e) { + e.printStackTrace(); + return "AI分析暂时不可用,请稍后再试。错误信息:" + e.getMessage(); + } + } + + private String sendTencentHunyuanRequest(String systemPrompt, String userMessage, + String secretId, String secretKey, + AiProperties.TencentHunyuan hy) throws IOException { + String host = "hunyuan.tencentcloudapi.com"; + String service = "hunyuan"; + String action = "ChatCompletions"; + String version = "2023-09-01"; + String region = hy.getRegion(); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + Date now = new Date(); + String date = sdf.format(now); + String timestamp = String.valueOf(now.getTime() / 1000); + + JSONObject requestBody = new JSONObject(); + requestBody.put("Model", hy.getModel()); + + JSONArray messages = new JSONArray(); + JSONObject systemMsg = new JSONObject(); + systemMsg.put("Role", "system"); + systemMsg.put("Content", systemPrompt); + messages.add(systemMsg); + + JSONObject userMsg = new JSONObject(); + userMsg.put("Role", "user"); + userMsg.put("Content", userMessage); + messages.add(userMsg); + + requestBody.put("Messages", messages); + requestBody.put("Temperature", properties.getTemperature()); + requestBody.put("TopP", properties.getTopP()); + + String payload = requestBody.toJSONString(); + String authorization = generateTc3Authorization(secretId, secretKey, timestamp, date, payload, host, service); + + Request request = new Request.Builder() + .url("https://" + host) + .post(RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"))) + .addHeader("Authorization", authorization) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Host", host) + .addHeader("X-TC-Action", action) + .addHeader("X-TC-Version", version) + .addHeader("X-TC-Timestamp", timestamp) + .addHeader("X-TC-Region", region) + .build(); + + try (Response response = client.newCall(request).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + if (!response.isSuccessful()) { + return "{\"Response\":{\"Error\":{\"Message\":\"HTTP " + response.code() + ": " + responseBody + "\"}}}"; + } + return responseBody; + } + } + + private String generateTc3Authorization(String secretId, String secretKey, String timestamp, + String date, String payload, String host, String service) { + try { + String canonicalUri = "/"; + String canonicalQueryString = ""; + String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + host + "\n"; + String signedHeaders = "content-type;host"; + String hashedRequestPayload = sha256Hex(payload); + + String canonicalRequest = "POST\n" + + canonicalUri + "\n" + + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + + signedHeaders + "\n" + + hashedRequestPayload; + + String algorithm = "TC3-HMAC-SHA256"; + String credentialScope = date + "/" + service + "/tc3_request"; + String hashedCanonicalRequest = sha256Hex(canonicalRequest); + String stringToSign = algorithm + "\n" + + timestamp + "\n" + + credentialScope + "\n" + + hashedCanonicalRequest; + + byte[] secretDate = hmacSha256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date); + byte[] secretService = hmacSha256(secretDate, service); + byte[] secretSigning = hmacSha256(secretService, "tc3_request"); + String signature = bytesToHex(hmacSha256(secretSigning, stringToSign)); + + return algorithm + " " + + "Credential=" + secretId + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + + "Signature=" + signature; + } catch (Exception e) { + throw new RuntimeException("Failed to generate authorization", e); + } + } + + private String parseTencentHunyuanResponse(String response) { + try { + JSONObject jsonResponse = JSON.parseObject(response); + JSONObject responseObj = jsonResponse.getJSONObject("Response"); + + if (responseObj.containsKey("Error")) { + JSONObject error = responseObj.getJSONObject("Error"); + return "AI服务错误:" + error.getString("Message"); + } + + JSONArray choices = responseObj.getJSONArray("Choices"); + if (choices != null && !choices.isEmpty()) { + JSONObject choice = choices.getJSONObject(0); + JSONObject message = choice.getJSONObject("Message"); + return message.getString("Content"); + } + + return "AI未返回有效内容"; + } catch (Exception e) { + return "解析AI响应失败:" + e.getMessage(); + } + } + + private static String sha256Hex(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hash); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static byte[] hmacSha256(byte[] key, String message) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + return mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} diff --git a/src/main/java/xuan/service/HunYuanAiService.java b/src/main/java/xuan/service/HunYuanAiService.java deleted file mode 100644 index 85be75d..0000000 --- a/src/main/java/xuan/service/HunYuanAiService.java +++ /dev/null @@ -1,186 +0,0 @@ -package xuan.service; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import okhttp3.*; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.TimeUnit; - -public class HunYuanAiService { - - private static final String SECRET_ID = System.getenv("TENCENT_SECRET_ID"); - private static final String SECRET_KEY = System.getenv("TENCENT_SECRET_KEY"); - private static final String SERVICE = "hunyuan"; - private static final String HOST = "hunyuan.tencentcloudapi.com"; - private static final String REGION = "ap-guangzhou"; - private static final String ACTION = "ChatCompletions"; - private static final String VERSION = "2023-09-01"; - - private static final OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(60, TimeUnit.SECONDS) - .readTimeout(120, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .build(); - - public String chat(String systemPrompt, String userMessage) { - if (SECRET_ID == null || SECRET_KEY == null) { - return "AI服务未配置,请设置TENCENT_SECRET_ID和TENCENT_SECRET_KEY环境变量。"; - } - try { - String response = sendRequest(systemPrompt, userMessage); - return parseResponse(response); - } catch (Exception e) { - e.printStackTrace(); - return "AI分析暂时不可用,请稍后再试。错误信息:" + e.getMessage(); - } - } - - private String sendRequest(String systemPrompt, String userMessage) throws IOException { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - Date now = new Date(); - String date = sdf.format(now); - String timestamp = String.valueOf(now.getTime() / 1000); - - JSONObject requestBody = new JSONObject(); - requestBody.put("Model", "hunyuan-lite"); - - JSONArray messages = new JSONArray(); - - JSONObject systemMsg = new JSONObject(); - systemMsg.put("Role", "system"); - systemMsg.put("Content", systemPrompt); - messages.add(systemMsg); - - JSONObject userMsg = new JSONObject(); - userMsg.put("Role", "user"); - userMsg.put("Content", userMessage); - messages.add(userMsg); - - requestBody.put("Messages", messages); - requestBody.put("Temperature", 0.7); - requestBody.put("TopP", 0.8); - - String payload = requestBody.toJSONString(); - - String authorization = generateAuthorization(timestamp, date, payload); - - Request request = new Request.Builder() - .url("https://" + HOST) - .post(RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"))) - .addHeader("Authorization", authorization) - .addHeader("Content-Type", "application/json; charset=utf-8") - .addHeader("Host", HOST) - .addHeader("X-TC-Action", ACTION) - .addHeader("X-TC-Version", VERSION) - .addHeader("X-TC-Timestamp", timestamp) - .addHeader("X-TC-Region", REGION) - .build(); - - try (Response response = client.newCall(request).execute()) { - String responseBody = response.body() != null ? response.body().string() : ""; - if (!response.isSuccessful()) { - return "{\"Response\":{\"Error\":{\"Message\":\"HTTP " + response.code() + ": " + responseBody + "\"}}}"; - } - return responseBody; - } - } - - private String generateAuthorization(String timestamp, String date, String payload) { - try { - String httpRequestMethod = "POST"; - String canonicalUri = "/"; - String canonicalQueryString = ""; - String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + HOST + "\n"; - String signedHeaders = "content-type;host"; - - String hashedRequestPayload = sha256Hex(payload); - - String canonicalRequest = httpRequestMethod + "\n" + - canonicalUri + "\n" + - canonicalQueryString + "\n" + - canonicalHeaders + "\n" + - signedHeaders + "\n" + - hashedRequestPayload; - - String algorithm = "TC3-HMAC-SHA256"; - String credentialScope = date + "/" + SERVICE + "/tc3_request"; - String hashedCanonicalRequest = sha256Hex(canonicalRequest); - String stringToSign = algorithm + "\n" + - timestamp + "\n" + - credentialScope + "\n" + - hashedCanonicalRequest; - - byte[] secretDate = hmacSha256(("TC3" + SECRET_KEY).getBytes(StandardCharsets.UTF_8), date); - byte[] secretService = hmacSha256(secretDate, SERVICE); - byte[] secretSigning = hmacSha256(secretService, "tc3_request"); - String signature = bytesToHex(hmacSha256(secretSigning, stringToSign)); - - return algorithm + " " + - "Credential=" + SECRET_ID + "/" + credentialScope + ", " + - "SignedHeaders=" + signedHeaders + ", " + - "Signature=" + signature; - } catch (Exception e) { - throw new RuntimeException("Failed to generate authorization", e); - } - } - - private String parseResponse(String response) { - try { - JSONObject jsonResponse = JSON.parseObject(response); - JSONObject responseObj = jsonResponse.getJSONObject("Response"); - - if (responseObj.containsKey("Error")) { - JSONObject error = responseObj.getJSONObject("Error"); - return "AI服务错误:" + error.getString("Message"); - } - - JSONArray choices = responseObj.getJSONArray("Choices"); - if (choices != null && !choices.isEmpty()) { - JSONObject choice = choices.getJSONObject(0); - JSONObject message = choice.getJSONObject("Message"); - return message.getString("Content"); - } - - return "AI未返回有效内容"; - } catch (Exception e) { - return "解析AI响应失败:" + e.getMessage(); - } - } - - private static String sha256Hex(String input) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); - return bytesToHex(hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static byte[] hmacSha256(byte[] key, String message) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(key, "HmacSHA256")); - return mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 70f8d81..5c18888 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -24,3 +24,21 @@ wechat.secret=${WECHAT_SECRET:} wechat.web.appid=${WECHAT_WEB_APPID:${WECHAT_APPID:}} wechat.web.secret=${WECHAT_WEB_SECRET:${WECHAT_SECRET:}} wechat.web.callback-base-url=${WECHAT_WEB_CALLBACK_BASE_URL:https://api.bemover.xyz} + +# AI Configuration +# Provider: openai-compatible | tencent-hunyuan +ai.provider=${AI_PROVIDER:openai-compatible} +ai.api-key=${AI_API_KEY:} +ai.base-url=${AI_BASE_URL:https://api.openai.com} +ai.model=${AI_MODEL:gpt-3.5-turbo} +ai.temperature=0.7 +ai.top-p=0.8 +ai.max-tokens=2048 +ai.connect-timeout=60 +ai.read-timeout=120 + +# Tencent Hunyuan (used when ai.provider=tencent-hunyuan) +ai.tencent-hunyuan.secret-id=${TENCENT_SECRET_ID:} +ai.tencent-hunyuan.secret-key=${TENCENT_SECRET_KEY:} +ai.tencent-hunyuan.region=ap-guangzhou +ai.tencent-hunyuan.model=hunyuan-lite diff --git a/src/main/resources/application.properties.template b/src/main/resources/application.properties.template new file mode 100644 index 0000000..9a8c026 --- /dev/null +++ b/src/main/resources/application.properties.template @@ -0,0 +1,44 @@ +server.port=8080 +spring.application.name=xuan-utils-pro + +# MySQL Database Configuration +spring.datasource.url=jdbc:mysql://localhost:3306/xuan_deduct?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true +spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver +spring.datasource.username=YOUR_DB_USERNAME +spring.datasource.password=YOUR_DB_PASSWORD + +# JPA Configuration +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true + +# Logging +logging.level.xuan=DEBUG + +# WeChat Mini Program Configuration +wechat.appid=${WECHAT_APPID:YOUR_WECHAT_APPID} +wechat.secret=${WECHAT_SECRET:YOUR_WECHAT_SECRET} + +# WeChat H5 QR Login Configuration +wechat.web.appid=${WECHAT_WEB_APPID:${WECHAT_APPID:}} +wechat.web.secret=${WECHAT_WEB_SECRET:${WECHAT_SECRET:}} +wechat.web.callback-base-url=${WECHAT_WEB_CALLBACK_BASE_URL:https://api.bemover.xyz} + +# AI Configuration +# Provider: openai-compatible | tencent-hunyuan +ai.provider=${AI_PROVIDER:openai-compatible} +ai.api-key=${AI_API_KEY:YOUR_AI_API_KEY} +ai.base-url=${AI_BASE_URL:https://api.openai.com} +ai.model=${AI_MODEL:gpt-3.5-turbo} +ai.temperature=0.7 +ai.top-p=0.8 +ai.max-tokens=2048 +ai.connect-timeout=60 +ai.read-timeout=120 + +# Tencent Hunyuan (used when ai.provider=tencent-hunyuan) +ai.tencent-hunyuan.secret-id=${TENCENT_SECRET_ID:YOUR_TENCENT_SECRET_ID} +ai.tencent-hunyuan.secret-key=${TENCENT_SECRET_KEY:YOUR_TENCENT_SECRET_KEY} +ai.tencent-hunyuan.region=ap-guangzhou +ai.tencent-hunyuan.model=hunyuan-lite From 7570437b209e22cd34c42d7766883ae0b6d1c056 Mon Sep 17 00:00:00 2001 From: H1d3rOne Date: Thu, 7 May 2026 04:10:28 +0800 Subject: [PATCH 7/8] =?UTF-8?q?chore:=20=E4=BB=8Egit=E8=B7=9F=E8=B8=AA?= =?UTF-8?q?=E4=B8=AD=E7=A7=BB=E9=99=A4application.properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 44 ----------------------- 1 file changed, 44 deletions(-) delete mode 100644 src/main/resources/application.properties diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 5c18888..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,44 +0,0 @@ -server.port=8080 -spring.application.name=xuan-utils-pro - -# MySQL Database Configuration -spring.datasource.url=jdbc:mysql://localhost:3306/xuan_deduct?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true -spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver -spring.datasource.username=xuan_user -spring.datasource.password=xuan_password - -# JPA Configuration -spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true - -# Logging -logging.level.xuan=DEBUG - -# WeChat Mini Program Configuration -wechat.appid=${WECHAT_APPID:} -wechat.secret=${WECHAT_SECRET:} - -# WeChat H5 QR Login Configuration -wechat.web.appid=${WECHAT_WEB_APPID:${WECHAT_APPID:}} -wechat.web.secret=${WECHAT_WEB_SECRET:${WECHAT_SECRET:}} -wechat.web.callback-base-url=${WECHAT_WEB_CALLBACK_BASE_URL:https://api.bemover.xyz} - -# AI Configuration -# Provider: openai-compatible | tencent-hunyuan -ai.provider=${AI_PROVIDER:openai-compatible} -ai.api-key=${AI_API_KEY:} -ai.base-url=${AI_BASE_URL:https://api.openai.com} -ai.model=${AI_MODEL:gpt-3.5-turbo} -ai.temperature=0.7 -ai.top-p=0.8 -ai.max-tokens=2048 -ai.connect-timeout=60 -ai.read-timeout=120 - -# Tencent Hunyuan (used when ai.provider=tencent-hunyuan) -ai.tencent-hunyuan.secret-id=${TENCENT_SECRET_ID:} -ai.tencent-hunyuan.secret-key=${TENCENT_SECRET_KEY:} -ai.tencent-hunyuan.region=ap-guangzhou -ai.tencent-hunyuan.model=hunyuan-lite From 41da982068958d9a2928f9a450f0a0ec0f053cbd Mon Sep 17 00:00:00 2001 From: H1d3rOne Date: Thu, 7 May 2026 04:11:28 +0800 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0CORS=E8=B7=A8?= =?UTF-8?q?=E5=9F=9F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/xuan/config/WebConfig.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/xuan/config/WebConfig.java b/src/main/java/xuan/config/WebConfig.java index 6e3ebb7..be0cf24 100644 --- a/src/main/java/xuan/config/WebConfig.java +++ b/src/main/java/xuan/config/WebConfig.java @@ -1,11 +1,21 @@ package xuan.config; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .maxAge(3600); + } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) {