feat: add core entities, controllers, services, and mappers across multi-module project

This commit is contained in:
zhangsz committed 2026-09-20 15:03:05 +08:00
commit 4335344af7
1456 files changed
+58308

No files matched your search

+1
View File
@@ -0,0 +1 @@
test
+8
View File
@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="admin" />
<module name="applet" />
<module name="common" />
<module name="mobileApp" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="8">
<module name="udpServer" target="1.5" />
</bytecodeTargetLevel>
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="uni_ai_doctor@139.9.2.195" uuid="06e507fc-985b-4560-a7ce-efbeebdf9230">
<driver-ref>mysql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://139.9.2.195:3306/uni_ai_doctor</jdbc-url>
<driver-properties>
<property name="autoReconnect" value="true" />
<property name="zeroDateTimeBehavior" value="convertToNull" />
<property name="tinyInt1isBit" value="false" />
<property name="characterEncoding" value="utf8" />
<property name="characterSetResults" value="utf8" />
<property name="yearIsDateType" value="false" />
</driver-properties>
</data-source>
</component>
</project>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM">
<file url="file://$PROJECT_DIR$" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/admin" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/admin/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/admin/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/applet" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/applet/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/applet/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/mobileApp" charset="UTF-8" />
</component>
</project>
+102
View File
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PersistentConfig">
<option name="historyConfigList">
<map>
<entry key="sys_dict">
<value>
<Config>
<option name="comment" value="true" />
<option name="daoMvnPath" value="" />
<option name="daoName" value="SysDictMapper" />
<option name="daoPackage" value="mapper" />
<option name="daoPostfix" value="Mapper" />
<option name="daoTargetFolder" value="$PROJECT_DIR$/applet/src/main/java/cn/unizk" />
<option name="modelMvnPath" value="" />
<option name="modelName" value="SysDict" />
<option name="modelPackage" value="po" />
<option name="modelTargetFolder" value="$PROJECT_DIR$/common/src/main/java/cn/unizk/entity" />
<option name="mysql_8" value="true" />
<option name="name" value="sys_dict" />
<option name="needToStringHashcodeEquals" value="true" />
<option name="offsetLimit" value="true" />
<option name="overrideXML" value="true" />
<option name="primaryKey" value="dict_code" />
<option name="projectFolder" value="$PROJECT_DIR$" />
<option name="tableName" value="sys_dict" />
<option name="useSchemaPrefix" value="true" />
<option name="xmlMvnPath" value="" />
<option name="xmlPackage" value="mapper" />
<option name="xmlTargetFolder" value="E:\code\unizk\AI_Questioning\applet\src\main\resources" />
</Config>
</value>
</entry>
<entry key="xunfu_account">
<value>
<Config>
<option name="comment" value="true" />
<option name="daoMvnPath" value="src/main/java" />
<option name="daoName" value="XunfuAccountDao" />
<option name="daoPackage" value="mapper" />
<option name="daoPostfix" value="Dao" />
<option name="daoTargetFolder" value="$PROJECT_DIR$/applet/src/main/java/cn/unizk" />
<option name="modelMvnPath" value="src/main/java" />
<option name="modelName" value="XunfuAccount" />
<option name="modelPackage" value="po" />
<option name="modelTargetFolder" value="$PROJECT_DIR$/common/src/main/java/cn/unizk/entity" />
<option name="name" value="xunfu_account" />
<option name="needToStringHashcodeEquals" value="true" />
<option name="offsetLimit" value="true" />
<option name="overrideXML" value="true" />
<option name="primaryKey" value="xunfu_account_id" />
<option name="projectFolder" value="$PROJECT_DIR$" />
<option name="tableName" value="xunfu_account" />
<option name="useExample" value="true" />
<option name="useSchemaPrefix" value="true" />
<option name="xmlMvnPath" value="src/main/resources" />
<option name="xmlPackage" value="mapper" />
<option name="xmlTargetFolder" value="E:\code\unizk\AI_Questioning\applet\src\main\resources" />
</Config>
</value>
</entry>
<entry key="xunfu_account_image">
<value>
<Config>
<option name="comment" value="true" />
<option name="daoMvnPath" value="" />
<option name="daoName" value="XunfuAccountImageDao" />
<option name="daoPackage" value="cn.unizk.mapper" />
<option name="daoPostfix" value="Dao" />
<option name="daoTargetFolder" value="$PROJECT_DIR$/applet/src/main/java/cn/unizk/mapper" />
<option name="modelMvnPath" value="src/main/java" />
<option name="modelName" value="XunfuAccountImage" />
<option name="modelPackage" value="cn.unizk.entity.po" />
<option name="modelTargetFolder" value="$PROJECT_DIR$/common/src/main/java/cn/unizk/entity/po" />
<option name="name" value="xunfu_account_image" />
<option name="overrideXML" value="true" />
<option name="primaryKey" value="image_id" />
<option name="projectFolder" value="$PROJECT_DIR$" />
<option name="tableName" value="xunfu_account_image" />
<option name="useExample" value="true" />
<option name="useSchemaPrefix" value="true" />
<option name="xmlMvnPath" value="" />
<option name="xmlPackage" value="generator" />
<option name="xmlTargetFolder" value="E:\code\unizk\AI_Questioning\applet\src\main\resources\mapper" />
</Config>
</value>
</entry>
</map>
</option>
<option name="users">
<map>
<entry key="jdbc:mysql://139.9.2.195:3306/uni_ai_doctor">
<value>
<User>
<option name="username" value="unizk" />
</User>
</value>
</entry>
</map>
</option>
</component>
</project>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
</profile>
</component>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="http://maven.aliyun.com/nexus/content/groups/public" />
</remote-repository>
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+124
View File
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4" />
+36
View File
@@ -0,0 +1,36 @@
# AI智能问诊
#### Description
尤尼智康-AI智能问诊项目
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+37
View File
@@ -0,0 +1,37 @@
# AI智能问诊
#### 介绍
尤尼智康-AI智能问诊项目
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4" />
+12807
View File
File diff suppressed because it is too large. Load diff
+124
View File
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>AI_Questioning</artifactId>
<groupId>cn.unizk</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>admin</artifactId>
<name>admin</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.davidcarboni</groupId>
<artifactId>encrypted-</artifactId>
</dependency>
<dependency>
<groupId>cn.unizk</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-ip</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,17 @@
package cn.unizk;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@SpringBootApplication
@EnableWebSecurity
@MapperScan("cn.unizk.mapper")
@EnableScheduling
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(AdminApp.class,args);
}
}
@@ -0,0 +1,10 @@
package cn.unizk.annotation;
import java.lang.annotation.*;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous
{
}
@@ -0,0 +1,32 @@
package cn.unizk.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author hao
* @Date 2026/3/13 15:34
* @Version 1.0
**/
@ConfigurationProperties(prefix = "huifu-vendor")
@Component
@Getter
@Setter
public class HuiFuVendorProperties {
private String sysId;
private String huifuId;
private String publicKey;
private String privateKey;
private String refundApi;
private String productId;
private String notifyUrl;
}
@@ -0,0 +1,36 @@
package cn.unizk.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* @Author hao
* @Date 2025/8/25 18:30
* @Version 1.0
**/
@Configuration
public class ResourcesConfig {
@Bean
public CorsFilter corsFilter()
{
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}
@@ -0,0 +1,150 @@
package cn.unizk.config;
import cn.unizk.security.filter.JwtAuthenticationTokenFilter;
import cn.unizk.properties.PermitAllUrlProperties;
import cn.unizk.security.handler.AuthenticationEntryPointImpl;
import cn.unizk.security.handler.LogoutSuccessHandlerImpl;
import cn.unizk.security.handler.UserAccessDeniedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
/**
* @Author hao
* @Date 2025/8/25 10:55
* @Version 1.0
**/
@SuppressWarnings("all")
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig {
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
@Autowired
private UserAccessDeniedHandler userAccessDeniedHandler;
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* 允许匿名访问的地址
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(userAccessDeniedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/pc/loginByUsername", "/register", "/captchaImage", "/wx/**","/h5/**","/pc/test","/satoken/ajaxlogin",
"/equipment/warehousing","/pc/userTreat/**").permitAll()
.antMatchers("/pc/admin/**").hasRole("ADMIN")
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/pc/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
/*.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)*/
.build();
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,253 @@
package cn.unizk.config;
import cn.unizk.entity.po.DeviceUpgrade;
import cn.unizk.mapper.DeviceUpgradeMapper;
import cn.unizk.service.FileService;
import cn.unizk.utils.FixedLengthSerializer;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.ip.dsl.Tcp;
import org.springframework.integration.ip.tcp.connection.TcpNetServerConnectionFactory;
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/11/18 11:55
* @Version 1.0
**/
//@Configuration
public class TcpServerDSLConfig {
private static final Logger logger = LoggerFactory.getLogger(TcpServerDSLConfig.class);
// private static final String UPGRADE_PACKAGE_ROOT_PATH = "/opt/jkadmin/upgrade/";
private static final String UPGRADE_PACKAGE_ROOT_PATH = "E:\\opt\\";
@Resource
private FileService fileService;
@Resource
private DeviceUpgradeMapper deviceUpgradeMapper;
@Bean
public TcpNetServerConnectionFactory serverConnectionFactory() {
TcpNetServerConnectionFactory factory = new TcpNetServerConnectionFactory(55367);
factory.setSingleUse(false);
factory.setSoTimeout(60_000);
factory.setSoKeepAlive(true);
factory.setSoLinger(3);
factory.setSoReceiveBufferSize(64 * 1024);
factory.setSoSendBufferSize(64* 1024);
factory.setSoTcpNoDelay(true);
ByteArrayCrLfSerializer deserializer = new ByteArrayCrLfSerializer();
deserializer.setMaxMessageSize(8192);
/*ByteArrayLengthHeaderSerializer ser = new ByteArrayLengthHeaderSerializer();
ser.setMaxMessageSize(1024 * 1024);*/
// factory.setSerializer(new FixedLengthSerializer());
factory.setDeserializer(deserializer);
return factory;
}
@Bean
public IntegrationFlow tcpServerFlow(){
return IntegrationFlows
.from(Tcp.inboundGateway(serverConnectionFactory()))
.log(LoggingHandler.Level.INFO, "TCP接收",
message -> "来自 " +
message.getHeaders().get("ip_address") +
" 的数据")
.<byte[],byte[]>transform(payload -> {
logger.info("收到字节长度为:{}",payload.length);
return payload;
})
.<byte[],String>transform(payload ->
new String(payload,StandardCharsets.UTF_8))
.transform(this::processBusinessLogic)
.handle(((payload, headers) -> {
/*return MessageBuilder.withPayload(payload.toString().getBytes())
.copyHeaders(headers)
.build();*/
if (payload instanceof byte[]) {
return MessageBuilder.withPayload((byte[]) payload)
.copyHeaders(headers)
.build();
} else {
return MessageBuilder.withPayload(payload.toString().getBytes())
.copyHeaders(headers)
.build();
}
}))
.get();
}
/**
* 构建数据包响应
* @param packageIndex 分包索引(00-31)
* @param data 1024字节数据
* @return 完整的响应数据包
*/
private byte[] buildDataPacket(String packageIndex, byte[] data) {
if (data.length != 1024){
logger.error("数据包大小不正确: {}, 应该是1024", data.length);
return new byte[0];
}
try {
// 协议格式:$|PN{index}|DA{1024字节二进制数据}|{CRC}\r\n
String crc = calculateCRC(data);
String header = String.format("$|PN%s|DA", packageIndex);
byte[] headerBytes = header.getBytes(StandardCharsets.US_ASCII);
// CRC部分:|{CRC}
String crcPart = "|" + crc;
byte[] crcBytes = crcPart.getBytes(StandardCharsets.US_ASCII);
// 完整数据包 = 协议头 + 1024字节文件数据 + CRC
byte[] response = new byte[headerBytes.length + data.length + crcBytes.length];
// 组装
System.arraycopy(headerBytes, 0, response, 0, headerBytes.length);
System.arraycopy(data, 0, response, headerBytes.length, data.length);
System.arraycopy(crcBytes, 0, response, headerBytes.length + data.length, crcBytes.length);
logger.info("构建数据包:索引={}, 总长度={}, CRC={}",
packageIndex, response.length, crc);
logger.info("========== 发送数据包详细信息 ==========");
logger.info("包索引: {}", packageIndex);
logger.info("协议头字符串: {}", header);
logger.info("协议头字节数: {}", headerBytes.length);
logger.info("数据字节数: {}", data.length);
logger.info("CRC字符串: {}", crc);
logger.info("CRC字节数: {}", crcBytes.length);
logger.info("总长度: {}", response.length);
// 打印完整十六进制(前200字节和后50字节)
StringBuilder hexFull = new StringBuilder();
int printLen = Math.min(1036, response.length);
for (int i = 0; i < printLen; i++) {
hexFull.append(String.format("%02X ", response[i]));
if ((i + 1) % 16 == 0) {
hexFull.append("\n");
}
}
logger.info("前{}字节十六进制:\n{}", printLen, hexFull.toString());
// 打印协议头部分的ASCII
byte[] headerOnly = new byte[headerBytes.length];
System.arraycopy(response, 0, headerOnly, 0, headerBytes.length);
logger.info("协议头ASCII: {}", new String(headerOnly, StandardCharsets.US_ASCII));
// 打印数据部分的前20字节
StringBuilder dataHex = new StringBuilder();
for (int i = 0; i < Math.min(20, data.length); i++) {
dataHex.append(String.format("%02X ", data[i]));
}
logger.info("数据部分前20字节: {}", dataHex.toString());
// 打印CRC部分
byte[] crcOnly = new byte[crcBytes.length];
System.arraycopy(response, headerBytes.length + data.length, crcOnly, 0, crcBytes.length);
logger.info("CRC部分ASCII: {}", new String(crcOnly, StandardCharsets.US_ASCII));
logger.info("======================================");
return response;
} catch (Exception e) {
logger.error("构建数据包失败", e);
return new byte[0];
}
}
private String calculateCRC(byte[] data) {
byte crc = 0;
for (byte b : data) {
crc ^= b;
}
return String.format("%02X", crc & 0xFF);
}
private Object processBusinessLogic(String input) {
//解析$|MUxxxx|HWxxxx|SWxxxx|UPxx|UQyy|*CRC\r\n
/**
* 去查software_upgrade表的当前版本的软件硬件号,如果和input解析不一致则升级
*去/opt/jkadmin/upgrade下面拿到升级包,调fileService拆包
* List<bytes[1024]> packages = fileService.splitFileWithNIO("/opt/jkadmin/upgrade/filename.bin")
* byte[] pack = packages.get(0)
* String s = new String(pack)
* 发送:$|PN00|DA{s}|{CRC}
*/
logger.info("处理业务数据: {}", input);
if (StringUtils.isBlank(input)){
return "";
}
DeviceUpgrade deviceUpgrade = deviceUpgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getBeCurrentVersion, 1));
if (Objects.isNull(deviceUpgrade)){
return input;
}
String fileUrl = UPGRADE_PACKAGE_ROOT_PATH + deviceUpgrade.getFileName();
List<byte[]> packages = null;
try {
packages = fileService.splitFileWithNIO(fileUrl);
} catch (IOException e) {
logger.error("分包发生异常",e);
return input;
}
if (CollectionUtils.isEmpty(packages)){
return input;
}
String[] items = input.split("\\|");
if (input.contains("HW") && input.contains("SW")){
logger.info("集线器回复包含HW和SW,回复第一个数据包");
//解析拿到硬件版本号和软件版本号,与device_upgrade表的当前版本对比,如果一致则不用升级,看这个逻辑需不需要吧,目前不加
String msg = "$|PN00|DA{s}|{CRC}";
byte[] bytes = packages.get(0);
//这里我是打算将bytes拼到msg上然后发送给硬件,不知道可不可行?crcHex是1024个字节的异或和
return buildDataPacket("00",bytes);
}else if (input.contains("ST")){
String msg = "$|PN{i}|DA{s}|{CRC}";
String i = null;
String st = null;
String pn = null;
for (String item : items) {
if (item.startsWith("UP")){
i = item.substring(2);
}
if (item.startsWith("ST")){
st = item.substring(2);
}
if (item.startsWith("PN")){
pn = item.substring(2);
}
}
if ("0".equals(st) && i != null){
if ("00".equals(i)){
//表示升级数据传输完成,是否需要断开连接
logger.info("升级数据传输完成");
return "";
}else {
byte[] bytes = packages.get(Integer.parseInt(i));
//这里我是打算将bytes拼到msg上然后发送给硬件,不知道可不可行?crcHex是1024个字节的异或和
return buildDataPacket(i,bytes);
}
}else if ("1".equals(st) && pn != null){
logger.info("集线器回复数据接收错误,重发第{}个数据包",pn);
byte[] bytes = packages.get(Integer.parseInt(pn));
//这里我是打算将bytes拼到msg上然后发送给硬件,不知道可不可行?crcHex是1024个字节的异或和
return buildDataPacket(pn,bytes);
}
}
return input;
}
}
@@ -0,0 +1,254 @@
package cn.unizk.config;
import cn.unizk.entity.po.DeviceUpgrade;
import cn.unizk.mapper.DeviceUpgradeMapper;
import cn.unizk.service.FileService;
import cn.unizk.utils.Crc16Util;
import cn.unizk.utils.XorUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.ip.dsl.Tcp;
import org.springframework.integration.ip.tcp.connection.TcpNetServerConnectionFactory;
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
import org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/10/30 14:58
* @Version 1.0
**/
@Configuration
public class TcpServerDSLConfig2 {
private static final Logger logger = LoggerFactory.getLogger(TcpServerDSLConfig.class);
private static final String UPGRADE_PACKAGE_ROOT_PATH = "/opt/jkadmin/upgrade/";
// private static final String UPGRADE_PACKAGE_ROOT_PATH = "E:\\opt\\";
@Resource
private FileService fileService;
@Resource
private DeviceUpgradeMapper deviceUpgradeMapper;
@Bean
public TcpNetServerConnectionFactory serverConnectionFactory() {
TcpNetServerConnectionFactory factory = new TcpNetServerConnectionFactory(55367);
factory.setSingleUse(false);
factory.setSoTimeout(60_000);
factory.setSoKeepAlive(true);
factory.setSoLinger(3);
factory.setSoReceiveBufferSize(64 * 1024);
factory.setSoSendBufferSize(64* 1024);
factory.setSoTcpNoDelay(true);
ByteArrayCrLfSerializer deserializer = new ByteArrayCrLfSerializer();
deserializer.setMaxMessageSize(8192);
/*ByteArrayLengthHeaderSerializer ser = new ByteArrayLengthHeaderSerializer();
ser.setMaxMessageSize(1024 * 1024);*/
factory.setSerializer(new ByteArrayRawSerializer());
factory.setDeserializer(deserializer);
return factory;
}
@Bean
public IntegrationFlow tcpServerFlow(){
return IntegrationFlows
.from(Tcp.inboundAdapter(serverConnectionFactory()))
.log(LoggingHandler.Level.INFO, "TCP接收",
message -> "来自 " +
message.getHeaders().get("ip_address") +
" 的数据")
.<byte[],byte[]>transform(payload -> {
logger.info("收到字节长度为:{}",payload.length);
return payload;
})
.<byte[],byte[]>transform(this::processBusinessLogic)
.handle(Tcp.outboundAdapter(serverConnectionFactory()))
.get();
}
/**
* 构建数据包响应
* @param packageIndex 分包索引(00-31)
* @param data 1024字节数据
* @return 完整的响应数据包
*/
private byte[] buildDataPacket(String packageIndex, byte[] data) {
if (data.length != 1024){
logger.error("数据包大小不正确: {}, 应该是1024", data.length);
return new byte[0];
}
try {
// 协议格式:$|PN{index}|DA{1024字节数据}|{CRC}\r\n
String crc = XorUtil.calculateCRC(data);
String header = String.format("$|PN%s|DA", packageIndex);
byte[] headerBytes = header.getBytes(StandardCharsets.US_ASCII);
// CRC部分:|{CRC}
String shuxian = "|";
byte[] shuxianBytes = shuxian.getBytes(StandardCharsets.US_ASCII);
byte[] crc16ToBytes = crc.getBytes(StandardCharsets.US_ASCII);
// 完整数据包 = 协议头 + 1024字节文件数据 + CRC
byte[] response = new byte[headerBytes.length + data.length + shuxianBytes.length + crc16ToBytes.length];
int destPos = 0;
// 组装
System.arraycopy(headerBytes, 0, response, destPos, headerBytes.length);
destPos += headerBytes.length;
System.arraycopy(data, 0, response, destPos, data.length);
destPos += data.length;
System.arraycopy(shuxianBytes, 0, response, destPos, shuxianBytes.length);
destPos += shuxianBytes.length;
System.arraycopy(crc16ToBytes,0,response,destPos,crc16ToBytes.length);
logger.info("构建数据包:索引={}, 总长度={}, CRC={},{}",
packageIndex, response.length, crc16ToBytes[0],crc16ToBytes[1]);
logger.info("========== 发送数据包详细信息 ==========");
logger.info("包索引: {}", packageIndex);
logger.info("协议头字符串: {}", header);
logger.info("协议头字节数: {}", headerBytes.length);
logger.info("数据字节数: {}", data.length);
logger.info("CRC字符串: {}", crc);
logger.info("CRC字节数: {}", crc16ToBytes.length);
logger.info("总长度: {}", response.length);
// 打印完整十六进制(前200字节和后50字节)
StringBuilder hexFull = new StringBuilder();
int printLen = Math.min(1036, response.length);
for (int i = 0; i < printLen; i++) {
hexFull.append(String.format("%02X ", response[i]));
if ((i + 1) % 16 == 0) {
hexFull.append("\n");
}
}
logger.info("前{}字节十六进制:\n{}", printLen, hexFull.toString());
// 打印协议头部分的ASCII
byte[] headerOnly = new byte[headerBytes.length];
System.arraycopy(response, 0, headerOnly, 0, headerBytes.length);
logger.info("协议头ASCII: {}", new String(headerOnly, StandardCharsets.US_ASCII));
// 打印数据部分的前20字节
StringBuilder dataHex = new StringBuilder();
for (int i = 0; i < Math.min(20, data.length); i++) {
dataHex.append(String.format("%02X ", data[i]));
}
logger.info("数据部分前20字节: {}", dataHex.toString());
// 打印CRC部分
byte[] crcOnly = new byte[crc16ToBytes.length];
System.arraycopy(response, headerBytes.length + data.length, crcOnly, 0, crc16ToBytes.length);
logger.info("CRC部分ASCII: {}", new String(crcOnly, StandardCharsets.US_ASCII));
logger.info("======================================");
return response;
} catch (Exception e) {
logger.error("构建数据包失败", e);
return new byte[0];
}
}
private byte[] processBusinessLogic(byte[] message) {
//解析$|MUxxxx|HWxxxx|SWxxxx|UPxx|UQyy|*CRC\r\n
/**
* 去查software_upgrade表的当前版本的软件硬件号,如果和input解析不一致则升级
*去/opt/jkadmin/upgrade下面拿到升级包,调fileService拆包
* List<bytes[1024]> packages = fileService.splitFileWithNIO("/opt/jkadmin/upgrade/filename.bin")
* byte[] pack = packages.get(0)
* String s = new String(pack)
* 发送:$|PN00|DA{s}|{CRC}
*/
String input = new String(message,StandardCharsets.US_ASCII);
logger.info("处理业务数据: {}", input);
if (StringUtils.isBlank(input)){
return new byte[0];
}
DeviceUpgrade deviceUpgrade = deviceUpgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getBeCurrentVersion, 1));
if (Objects.isNull(deviceUpgrade)){
return new byte[0];
}
String fileUrl = UPGRADE_PACKAGE_ROOT_PATH + deviceUpgrade.getFileName();
List<byte[]> packages = null;
try {
packages = fileService.splitFileWithNIO(fileUrl);
} catch (IOException e) {
logger.error("分包发生异常",e);
return new byte[0];
}
if (CollectionUtils.isEmpty(packages)){
return new byte[0];
}
String[] items = input.split("\\|");
if (input.contains("HW") && input.contains("SW")){
logger.info("集线器回复包含HW和SW,回复第一个数据包");
//解析拿到硬件版本号和软件版本号,与device_upgrade表的当前版本对比,如果一致则不用升级,看这个逻辑需不需要吧,目前不加
String msg = "$|PN00|DA{s}|{CRC}";
byte[] bytes = packages.get(0);
//这里我是打算将bytes拼到msg上然后发送给硬件,不知道可不可行?crcHex是1024个字节的异或和
return buildDataPacket("01",bytes);
}else if (input.contains("ST")){
String msg = "$|PN{i}|DA{s}|{CRC}";
String i = null;
String st = null;
String pn = null;
for (String item : items) {
if (item.startsWith("UP")){
i = item.substring(2);
}
if (item.startsWith("ST")){
st = item.substring(2);
}
if (item.startsWith("PN")){
pn = item.substring(2);
}
}
if ("0".equals(st) && i != null){
if ("00".equals(i)){
//表示升级数据传输完成,是否需要断开连接
logger.info("升级数据传输完成");
return new byte[0];
}else {
byte[] bytes = packages.get(Integer.parseInt(i)-1);
//这里我是打算将bytes拼到msg上然后发送给硬件,不知道可不可行?crcHex是1024个字节的异或和
return buildDataPacket(i,bytes);
}
}else if ("1".equals(st) && pn != null){
logger.info("集线器回复数据接收错误,重发第{}个数据包",pn);
byte[] bytes = packages.get(Integer.parseInt(pn));
//这里我是打算将bytes拼到msg上然后发送给硬件,不知道可不可行?crcHex是1024个字节的异或和
return buildDataPacket(pn,bytes);
}
}else {
String up = null;
String pn = null;
for (String item : items) {
if (item.startsWith("UP")){
up = item.substring(2);
}
if (item.startsWith("PN")){
pn = item.substring(2);
}
}
if ("00".equals(up) && "00".equals(pn)){
logger.info("升级数据传输完成");
return new byte[0];
}
}
return new byte[0];
}
}
@@ -0,0 +1,10 @@
package cn.unizk.constants;
/**
* @Author hao
* @Date 2025/8/22 18:52
* @Version 1.0
**/
public class CacheConstant {
public static final String LOGIN_TOKEN_KEY = "login_tokens";
}
@@ -0,0 +1,22 @@
package cn.unizk.constants;
/**
* @Author hao
* @Date 2025/8/22 18:51
* @Version 1.0
**/
public class Constants {
public static final String JWT_TOKEN_KEY = "login_client_key";
public static final String TOKEN_PREFIX = "Bearer";
public static final Integer MAX_USERNAME_LENGTH = 15;
public static final String ALL_PERMISSION = "*:*:*";
public static final Integer MIN_PASSWORD_LENGTH = 6;
public static final Integer MAX_PASSWORD_LENGTH = 15;
public static final String DEVICE_UPGRADE_PACKAGE_ROOT_PATH = "/opt/jkadmin/upgrade/";
}
@@ -0,0 +1,193 @@
package cn.unizk.controller;
import cn.unizk.entity.po.*;
import cn.unizk.entity.vo.*;
import cn.unizk.enums.OrderAfterSaleStatus;
import cn.unizk.enums.OrderStatus;
import cn.unizk.enums.PlatformType;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.MobileAppUserMapper;
import cn.unizk.mapper.MobileOrderItemMapper;
import cn.unizk.mapper.OrderReviewMapper;
import cn.unizk.service.ComboService;
import cn.unizk.service.ProductService;
import cn.unizk.service.SysOrderService;
import cn.unizk.utils.BeanCopyUtils;
import cn.unizk.utils.SecurityUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.*;
/**
* @Author hao
* @Date 2025/9/4 18:16
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin")
public class AdminOrderController {
@Resource
private SysOrderService sysOrderService;
@Resource
private ComboService comboService;
@Resource
private MobileOrderItemMapper mobileOrderItemMapper;
@Resource
private ProductService productService;
@Resource
private MobileAppUserMapper mobileAppUserMapper;
@Resource
private OrderReviewMapper reviewMapper;
@PostMapping("/orders")
public Page<MobileOrderVO> orders(@RequestBody UniPage<OrderReqVO> uniPage) {
return sysOrderService.orderList(uniPage);
}
@PostMapping("/editOrder")
@Transactional(rollbackFor = Exception.class)
public ResultData editOrder(@RequestParam("orderId") String orderId, @RequestParam("optionType") Integer optionType) {
MobileOrder wxOrder = sysOrderService.getById(orderId);
if (Objects.isNull(wxOrder)) {
throw new ServiceException("该订单不存在");
}
if (optionType == 1) {
//发货
if (!OrderStatus.paid.name().equals(wxOrder.getStatus())) {
throw new ServiceException(String.format("%s状态订单不支持发货", OrderStatus.valueOf(wxOrder.getStatus()).getValue()));
}
if (wxOrder.getProductId() == null) {
throw new ServiceException("该订单商品为非实体商品,不支持发货");
}
wxOrder.setStatus(OrderStatus.shipped.name());
sysOrderService.updateById(wxOrder);
}
if (optionType == 2) {
//核销
if (!OrderStatus.paid.name().equals(wxOrder.getStatus()) && !OrderStatus.unverified.name().equals(wxOrder.getStatus())) {
throw new ServiceException(String.format("%s状态订单不支持核销", OrderStatus.valueOf(wxOrder.getStatus()).getValue()));
}
if (wxOrder.getComboId() == null) {
throw new ServiceException("该订单商品为实体商品,不支持核销");
}
wxOrder.setStatus(OrderStatus.verified.name());
sysOrderService.updateById(wxOrder);
}
/*if (optionType == 3){
if (!OrderStatus.request_refund.name().equals(wxOrder.getStatus())){
throw new ServiceException("该订单非申请退款,不能做退款操作!");
}
if (OrderStatus.refunded.name().equals(wxOrder.getStatus())){
throw new ServiceException("该订单已做退款处理");
}
wxOrder.setStatus(OrderStatus.refunded.name());
sysOrderService.updateById(wxOrder);
}*/
return ResultData.success();
}
@GetMapping("/getOrderStatistics")
public Map<String, Long> getOrderStatistics() {
List<MobileOrder> wxOrders = sysOrderService.getBaseMapper().selectList(null);
Map<String, Long> map = new HashMap<>();
map.put("A", (long) wxOrders.size());
long p = wxOrders.stream().filter(o -> o.getStoreId() == null).count();
map.put("P", p);
long s = wxOrders.stream().filter(o -> o.getStoreId() != null).count();
map.put("S", s);
return map;
}
@GetMapping("/orderDetail")
public MobileOrderDetailVO detail(@RequestParam("orderId") String orderId) {
MobileOrderDetailVO vo = new MobileOrderDetailVO();
MobileOrder wxOrder = sysOrderService.getById(orderId);
BeanUtils.copyProperties(wxOrder, vo);
if (vo.getComboId() != null) {
List<MobileOrderItemVO> list = new ArrayList<>();
Combo combo = comboService.getById(vo.getComboId());
MobileOrderItemVO product = new MobileOrderItemVO();
product.setImage(combo.getImage());
product.setProductName(combo.getComboName());
product.setSingleProductPrice(combo.getComboPrice());
list.add(product);
vo.setOrderItemVOS(list);
} else {
List<MobileOrderItem> wxOrderItems = mobileOrderItemMapper.selectList(new LambdaQueryWrapper<MobileOrderItem>().eq(MobileOrderItem::getMobileOrderId, vo.getOrderId()));
if (CollectionUtils.isNotEmpty(wxOrderItems)){
Collection<MobileOrderItemVO> itemVOS = BeanCopyUtils.copyArrays(wxOrderItems, MobileOrderItemVO::new);
for (MobileOrderItemVO itemVO : itemVOS) {
if (itemVO.getProductId() == null) {
continue;
}
JkProduct jkProduct = productService.getById(itemVO.getProductId());
itemVO.setProductName(jkProduct.getProductName());
itemVO.setImage(jkProduct.getProductImage());
itemVO.setSingleProductPrice(jkProduct.getProductPrice());
}
vo.setOrderItemVOS((List<MobileOrderItemVO>) itemVOS);
}
}
MobileAppUser one = mobileAppUserMapper.selectById(vo.getMobileUserId());
vo.setMobileAppUser(one);
return vo;
}
@PostMapping("/agreeRefund")
public ResultData refund(@RequestParam("orderId") String orderId,@RequestParam("platformType") String platformType) {
if (!PlatformType.WECHAT_MP.name().equals(platformType) && !PlatformType.APP.name().equals(platformType)){
throw new ServiceException("未知的平台类型");
}
return sysOrderService.agreeRefund(orderId,platformType);
}
@PostMapping("/afterSales")
public Page<MobileOrderVO> afterSales(@RequestBody UniPage<OrderReqVO> uniPage) {
return sysOrderService.afterSale(uniPage);
}
@GetMapping("/getDeliveryTotal")
public Map<String, Long> getDeliveryTotal() {
SysUser sysUser = SecurityUtils.getSysUser();
Map<String, Long> map = new HashMap<>();
Long ps = sysOrderService.getBaseMapper().selectCount(new LambdaQueryWrapper<MobileOrder>().eq(MobileOrder::getDeliveryWay, "配送")
.eq(MobileOrder::getAfterSaleStatus, 0));
map.put("配送", ps);
Long zt = sysOrderService.getBaseMapper().selectCount(new LambdaQueryWrapper<MobileOrder>().eq(MobileOrder::getDeliveryWay, "自提")
.eq(MobileOrder::getAfterSaleStatus, 0));
map.put("核销", zt);
return map;
}
@PostMapping("/refundRefusal")
public boolean refundRefusal(@RequestBody MobileOrder order) {
MobileOrder wxOrder = sysOrderService.getById(order.getOrderId());
if (Objects.isNull(wxOrder)) {
throw new ServiceException("无此订单");
}
wxOrder.setRejectReason(order.getRejectReason());
wxOrder.setAfterSaleStatus(OrderAfterSaleStatus.REJECT_TUI_KUAN.getKey());
return sysOrderService.updateById(wxOrder);
}
@GetMapping("/getOrderReview")
public List<OrderReview> getOrderReview(@RequestParam("orderId") String orderId) {
return reviewMapper.selectList(new LambdaQueryWrapper<OrderReview>().eq(OrderReview::getOrderId, orderId));
}
/* @PostMapping("/appOrders")
public Page<MobileOrderVO> appOrders(@RequestBody UniPage<OrderReqVO> uniPage) {
Page<MobileOrderVO> page = (Page<MobileOrderVO>) uniPage.getPage();
return page;
}*/
}
@@ -0,0 +1,76 @@
package cn.unizk.controller;
import cn.unizk.entity.dto.LoginUser;
import cn.unizk.entity.po.Device;
import cn.unizk.entity.po.SysUser;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.service.DeviceService;
import cn.unizk.utils.SecurityUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/9/26 16:48
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/store/device")
public class ChildDeviceController {
@Resource
private DeviceService deviceService;
@GetMapping("/page")
public Page<Device> page(@RequestParam(value = "keyword",required = false) String keyword,
UniPage uniPage){
LoginUser loginUser = SecurityUtils.getLoginUser();
if (Objects.isNull(loginUser) || Objects.isNull(loginUser.getSysUser())){
throw new ServiceException(401,"登录已过期");
}
if (loginUser.getSysUser().getStoreId() == null){
return new Page<>();
}
Page<Device> page = new Page<>(uniPage.getCurrentPage(),uniPage.getPageSize());
return deviceService.page(page,new LambdaQueryWrapper<Device>()
.eq(Device::getStoreId,loginUser.getSysUser().getStoreId())
.and(StringUtils.isNotBlank(keyword),wrapper -> {
wrapper.eq(StringUtils.isNotBlank(keyword),Device::getMuid,keyword)
.or()
.eq(StringUtils.isNotBlank(keyword),Device::getDeviceId,keyword);
}));
}
@GetMapping("/getTotal")
public long getTotal(){
SysUser sysUser = SecurityUtils.getSysUser();
if (Objects.isNull(sysUser) || sysUser.getStoreId() == null){
return 0;
}
return deviceService.count(new LambdaQueryWrapper<Device>().eq(Device::getStoreId,sysUser.getStoreId()));
}
@PostMapping("/forcePaySwitch")
public ResultData forcePaySwitch(@RequestParam("deviceId") String deviceId,@RequestParam("forcePay") Integer forcePay){
if (forcePay != 0 && forcePay != 1){
throw new ServiceException("参数不合法");
}
Device device = new Device();
device.setDeviceId(deviceId);
device.setForcePay(forcePay);
if (forcePay == 1){
device.setStatus("0");
}else {
device.setStatus("1");
}
return deviceService.updateById(device) ? ResultData.success() : ResultData.failure();
}
}
@@ -0,0 +1,468 @@
package cn.unizk.controller;
import cn.hutool.core.date.DateUtil;
import cn.unizk.entity.dto.CashOutDTO;
import cn.unizk.entity.dto.CashOutResponse;
import cn.unizk.entity.po.*;
import cn.unizk.entity.vo.*;
import cn.unizk.enums.OrderAfterSaleStatus;
import cn.unizk.enums.OrderStatus;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.CashOutRecordMapper;
import cn.unizk.mapper.DeviceUsageRecordMapper;
import cn.unizk.mapper.StoreAccountMapper;
import cn.unizk.service.*;
import cn.unizk.utils.HttpUtil;
import cn.unizk.utils.QRcodeUtil;
import cn.unizk.utils.SecurityUtils;
import cn.unizk.utils.ServletUtils;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
/**
* @Author hao
* @Date 2025/9/26 10:46
* @Version 1.0
**/
@RestController
@Slf4j
@RequestMapping("/pc/store")
public class ChildStoreController {
@Resource
private DeviceService deviceService;
@Resource
private WxAuthService wxAuthService;
@Resource
private BusinessChannelService businessChannelService;
@Resource
private StoreService storeService;
@Resource
private ChildStoreService childStoreService;
@Resource
private DeviceUsageRecordMapper deviceUsageRecordMapper;
@Resource
private SysOrderService orderService;
@Resource
private PhysioService physioService;
@Resource
private StoreAccountMapper storeAccountMapper;
@Resource
private CashOutRecordMapper cashOutRecordMapper;
@PostMapping("/addAccountData")
public ResultData addAccountData(@Valid @RequestBody XunfuAccountVO vo){
if (Objects.isNull(vo)){
throw new ServiceException("参数不正确");
}
List<XunfuAccountImage> images = vo.getImages();
List<String> imageTypes = Lists.newArrayList("idCardFront","idCardBack","personWithIdCard","bankCard",
"license","openAccountLicenses");
return null;
}
@GetMapping("/getAppletQrCode")
public void getAppletQrCode(HttpServletResponse response, @RequestParam(value = "type",required = false) String type){
SysUser sysUser = SecurityUtils.getSysUser();
JkStore jkStore = storeService.getById(sysUser.getStoreId());
if (Objects.isNull(jkStore)){
log.error("无此门店:storeId={}",sysUser.toString());
throw new ServiceException("服务异常");
}
String accessToken = wxAuthService.getAccessToken();
String url = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s",accessToken);
log.info("url={}",url);
JSONObject jsonBody = JSONObject.of();
Integer channelId = jkStore.getChannelId();
jsonBody.put("scene",channelId);
// jsonBody.put("check_path",false);
// jsonBody.put("env_version","trial");
jsonBody.put("width",280);
byte[] bytes = HttpUtil.postRtnBytes(url, null, jsonBody);
if (bytes == null || bytes.length == 0){
ServletUtils.renderString(response,"qrcode res is empty");
return;
}
String fileName = "";
if (channelId != null){
BusinessChannel businessChannel = businessChannelService.getById(channelId);
fileName = Objects.nonNull(businessChannel) ? businessChannel.getChannelName() : "";
}
// response.setContentType("image/jpeg");
response.setContentLength(bytes.length);
if ("download".equals(type)){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
response.setHeader("Content-Disposition", "attachment; filename=渠道商-"+channelId+now+".png");
}
try (OutputStream os = response.getOutputStream()) {
os.write(bytes);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@GetMapping("/devicePage")
public Page<Device> devicePage(UniPage uniPage,@RequestParam(value = "keyword",required = false) String keyword){
Page<Device> page = new Page<>(uniPage.getCurrentPage(),uniPage.getPageSize());
if (StringUtils.isNotBlank(keyword)){
LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Device::getDeviceId,keyword).or().eq(Device::getMuid,keyword);
return deviceService.page(page,wrapper);
}else {
return deviceService.page(page);
}
}
@GetMapping("/usageRecord")
public Page<DeviceUsageRecordVO> usageRecord(@RequestParam("deviceId") String deviceId,UniPage uniPage){
Page<DeviceUsageRecord> page = new Page<>(uniPage.getCurrentPage(),uniPage.getPageSize());
LambdaQueryWrapper<DeviceUsageRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DeviceUsageRecord::getDeviceId,deviceId).orderByDesc(DeviceUsageRecord::getStartTime);
Page<DeviceUsageRecord> recordPage = deviceUsageRecordMapper.selectPage(page, wrapper);
List<DeviceUsageRecord> records = recordPage.getRecords();
List<DeviceUsageRecordVO> voList = new ArrayList<>();
for (DeviceUsageRecord record : records) {
DeviceUsageRecordVO vo = new DeviceUsageRecordVO();
BeanUtils.copyProperties(record,vo);
long minutes = Duration.between(record.getStartTime(), record.getEndTime()).toMinutes();
vo.setUseDuration(minutes);
voList.add(vo);
}
Page<DeviceUsageRecordVO> voPage = new Page<>();
voPage.setRecords(voList);
voPage.setCurrent(recordPage.getCurrent());
voPage.setSize(recordPage.getSize());
voPage.setTotal(recordPage.getTotal());
return voPage;
}
@PostMapping("/agreeRefund")
public ResultData refund(@RequestParam("orderId") String orderId){
SysUser sysUser = SecurityUtils.getSysUser();
MobileOrder wxOrder = orderService.getById(orderId);
if (Objects.isNull(wxOrder)){
throw new ServiceException(String.format("订单不存在,订单号:%s",orderId));
}
if (wxOrder.getStoreId() == null || !sysUser.getStoreId().equals(wxOrder.getStoreId())){
throw new ServiceException("警告:越权操作!");
}
return orderService.refund(wxOrder);
}
@PostMapping("/orderPage")
public Page<MobileOrderVO> orders(@RequestBody UniPage<OrderReqVO> uniPage){
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException("请求不合法");
}
OrderReqVO queryBody = uniPage.getQueryBody();
if (queryBody == null){
queryBody = new OrderReqVO();
}
queryBody.setStoreId(sysUser.getStoreId());
return orderService.orderList(uniPage);
}
@PostMapping("/editOrder")
@Transactional(rollbackFor = Exception.class)
public ResultData editOrder(@RequestParam("orderId") String orderId,@RequestParam("optionType") Integer optionType){
SysUser sysUser = SecurityUtils.getSysUser();
MobileOrder wxOrder = orderService.getById(orderId);
if (Objects.isNull(wxOrder)){
throw new ServiceException("该订单不存在");
}
if (sysUser == null || !sysUser.getStoreId().equals(wxOrder.getStoreId())){
throw new ServiceException("非法请求");
}
if (optionType == 1){
//发货
if (!OrderStatus.paid.name().equals(wxOrder.getStatus())){
throw new ServiceException(String.format("%s状态订单不支持发货",OrderStatus.valueOf(wxOrder.getStatus()).getValue()));
}
if (wxOrder.getProductId() == null) {
throw new ServiceException("该订单商品为非实体商品,不支持发货");
}
wxOrder.setStatus(OrderStatus.shipped.name());
orderService.updateById(wxOrder);
}
if (optionType == 2){
//核销
if (!OrderStatus.paid.name().equals(wxOrder.getStatus()) && !OrderStatus.unverified.name().equals(wxOrder.getStatus())){
throw new ServiceException(String.format("%s状态订单不支持核销",OrderStatus.valueOf(wxOrder.getStatus()).getValue()));
}
if (wxOrder.getComboId() == null){
throw new ServiceException("该订单商品为实体商品,不支持核销");
}
wxOrder.setStatus(OrderStatus.verified.name());
orderService.updateById(wxOrder);
}
/*if (optionType == 3){
if (!OrderStatus.request_refund.name().equals(wxOrder.getStatus())){
throw new ServiceException("该订单非申请退款,不能做退款操作!");
}
if (OrderStatus.refunded.name().equals(wxOrder.getStatus())){
throw new ServiceException("该订单已做退款处理");
}
wxOrder.setStatus(OrderStatus.refunded.name());
orderService.updateById(wxOrder);
}*/
return ResultData.success();
}
@GetMapping("/getDeliveryTotal")
public Map<String,Long> getDeliveryTotal(){
SysUser sysUser = SecurityUtils.getSysUser();
Map<String,Long> map = new HashMap<>();
Long ps = orderService.getBaseMapper().selectCount(new LambdaQueryWrapper<MobileOrder>().eq(MobileOrder::getDeliveryWay, "配送").eq(MobileOrder::getStoreId,sysUser.getStoreId()));
map.put("配送",ps);
Long zt = orderService.getBaseMapper().selectCount(new LambdaQueryWrapper<MobileOrder>().eq(MobileOrder::getDeliveryWay, "自提").eq(MobileOrder::getStoreId,sysUser.getStoreId()));
map.put("核销",zt);
return map;
}
@PostMapping("/afterSales")
public Page<MobileOrderVO> afterSale(@RequestBody UniPage<OrderReqVO> uniPage){
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException("请求不合法");
}
OrderReqVO queryBody = uniPage.getQueryBody();
if (queryBody == null){
queryBody = new OrderReqVO();
}
queryBody.setStoreId(sysUser.getStoreId());
return orderService.afterSale(uniPage);
}
@PostMapping("/refundRefusal")
public boolean refundRefusal(@RequestBody MobileOrder order){
SysUser sysUser = SecurityUtils.getSysUser();
MobileOrder wxOrder = orderService.getById(order.getOrderId());
if (Objects.isNull(wxOrder)){
throw new ServiceException("无此订单");
}
if (sysUser.getStoreId() == null || !sysUser.getStoreId().equals(wxOrder.getStoreId())){
throw new ServiceException("非法请求");
}
wxOrder.setRejectReason(order.getRejectReason());
wxOrder.setAfterSaleStatus(OrderAfterSaleStatus.REJECT_TUI_KUAN.getKey());
return orderService.updateById(wxOrder);
}
@PostMapping("/addPhysio")
public boolean addPhysio(@RequestBody JkPhysio jkPhysio){
return physioService.save(jkPhysio);
}
@PostMapping("/physioList")
public Page<JkPhysio> physioList(@RequestBody UniPage<PhysioVO> uniPage){
Integer storeId = authVerify();
Page<JkPhysio> page = (Page<JkPhysio>) uniPage.getPage();
LambdaQueryWrapper<JkPhysio> queryWrapper = uniPage.buildQueryWrapper(q -> {
LambdaQueryWrapper<JkPhysio> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JkPhysio::getStoreId, storeId);
wrapper.and(StringUtils.isNotBlank(q.getKeyword()), r -> {
r.eq(JkPhysio::getIdcard, q.getKeyword())
.or()
.eq(JkPhysio::getPhysioTel, q.getKeyword())
.or()
.eq(JkPhysio::getPhysioName, q.getKeyword());
});
wrapper.between(q.getCreateTime() != null, JkPhysio::getCreateTime, q.getSearchStartTime(), q.getSearchEndTime());
wrapper.eq(q.getAudit() != null, JkPhysio::getAudit, q.getAudit());
return wrapper;
});
return physioService.page(page,queryWrapper);
}
@PostMapping("/editPhysio")
public Boolean editPhysio(@RequestBody JkPhysio jkPhysio){
jkPhysio.setAudit(0);
return physioService.updateById(jkPhysio);
}
@PostMapping("/deletePhysio")
public boolean deletePhysio(@RequestParam("id") Integer id){
Integer storeId = authVerify();
JkPhysio byId = physioService.getById(id);
if (Objects.isNull(byId) || !storeId.equals(byId.getStoreId())){
throw new ServiceException(403,"非法访问");
}
return physioService.removeById(id);
}
private Integer authVerify(){
Integer storeId = SecurityUtils.getSysUser().getStoreId();
if (storeId == null){
throw new ServiceException(403,"非法访问");
}
return storeId;
}
@GetMapping("/getAppScanPayQRCode")
public void getAppScanPayQRCode(HttpServletResponse response,@RequestParam("deviceId") String deviceId,
@RequestParam("route") String route){
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException(403,"非法访问");
}
if (StringUtils.isBlank(deviceId)){
throw new ServiceException("deviceId cannot be empty!");
}
Device device = deviceService.getById(deviceId);
if (Objects.isNull(device)){
throw new ServiceException("ID->"+deviceId + "not exist");
}
String url = "uri=" + route + "&deviceId=" + deviceId + "&storeId=" + sysUser.getStoreId();
QRcodeUtil.generateQrcode(response,url,null);
}
@GetMapping("/getScanPayQRCode")
public void getScanPayQRCode(HttpServletResponse response,@RequestParam("deviceId") String deviceId) throws UnsupportedEncodingException {
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException(403,"非法访问");
}
if (StringUtils.isBlank(deviceId)){
throw new ServiceException("deviceId cannot be empty!");
}
Device device = deviceService.getById(deviceId);
if (Objects.isNull(device)){
throw new ServiceException("ID->"+deviceId + "not exist");
}
String accessToken = wxAuthService.getAccessToken();
String url = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s",accessToken);
log.info("url={}",url);
JSONObject jsonBody = JSONObject.of();
String scene = sysUser.getStoreId() + "$" + deviceId;
jsonBody.put("scene",scene);
if (device.getForcePay() == 0){
deviceId = "0";
}else {
jsonBody.put("page","pages/order/devicePayOnce");
}
jsonBody.put("width",280);
byte[] bytes = HttpUtil.postRtnBytes(url, null, jsonBody);
if (bytes == null || bytes.length == 0){
ServletUtils.renderString(response,"qrcode res is empty");
return;
}
response.setContentLength(bytes.length);
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String fileName = "设备ID-" + deviceId + "-" + now + ".png";
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
.replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
try (OutputStream os = response.getOutputStream()) {
os.write(bytes);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@GetMapping("/getGrfScanPayQRCode")
public void getGrfScanPayQRCode(HttpServletResponse response,@RequestParam("deviceId") String deviceId) throws UnsupportedEncodingException {
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException(403,"非法访问");
}
if (StringUtils.isBlank(deviceId)){
throw new ServiceException("deviceId cannot be empty!");
}
Device device = deviceService.getById(deviceId);
if (Objects.isNull(device)){
throw new ServiceException("ID->"+deviceId + "not exist");
}
String accessToken = wxAuthService.getAccessToken();
String url = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s",accessToken);
log.info("url={}",url);
JSONObject jsonBody = JSONObject.of();
String scene = sysUser.getStoreId() + "$" + deviceId;
jsonBody.put("scene",scene);
if (device.getForcePay() == 0){
deviceId = "0";
}else {
jsonBody.put("page","pages/memberRecharge/index");
}
jsonBody.put("width",280);
byte[] bytes = HttpUtil.postRtnBytes(url, null, jsonBody);
if (bytes == null || bytes.length == 0){
ServletUtils.renderString(response,"qrcode res is empty");
return;
}
response.setContentLength(bytes.length);
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String fileName = "设备ID-" + deviceId + "-" + now + ".png";
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
.replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
try (OutputStream os = response.getOutputStream()) {
os.write(bytes);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@GetMapping("/accounts")
public List<StoreAccountVO> accounts(){
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException(403,"权限不足");
}
List<StoreAccount> storeAccounts = storeAccountMapper.selectList(new LambdaQueryWrapper<StoreAccount>().eq(StoreAccount::getStoreId,sysUser.getStoreId()));
List<StoreAccountVO> vos = new ArrayList<>();
for (StoreAccount storeAccount : storeAccounts) {
StoreAccountVO accountVO = new StoreAccountVO();
BeanUtils.copyProperties(storeAccount,accountVO);
JkStore jkStore = storeService.getById(storeAccount.getStoreId());
if (jkStore != null){
accountVO.setAccountName(jkStore.getStoreName());
}
vos.add(accountVO);
}
return vos;
}
@PostMapping("/cashOut")
public CashOutResponse cashOut(@RequestBody CashOutDTO dto){
return childStoreService.profitSharingPay(dto);
}
@PostMapping("/cashOutRecord")
public Page<CashOutRecord> cashOutRecordPage(@RequestBody UniPage<CashOutRecord> uniPage){
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null){
throw new ServiceException(403,"权限不足");
}
Page<CashOutRecord> page = (Page<CashOutRecord>) uniPage.getPage();
return cashOutRecordMapper.selectPage(page,new LambdaQueryWrapper<CashOutRecord>().eq(CashOutRecord::getStoreId,sysUser.getStoreId()));
}
}
@@ -0,0 +1,155 @@
package cn.unizk.controller;
import cn.unizk.entity.po.Combo;
import cn.unizk.entity.po.SubHealthDietaryRegulation;
import cn.unizk.entity.vo.ComboVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.enums.OrderStatus;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.SubHealthDietaryRegulationMapper;
import cn.unizk.service.ComboService;
import cn.unizk.service.SysOrderService;
import cn.unizk.utils.AESUtil;
import cn.unizk.utils.SendSmsUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @Author hao
* @Date 2025/9/11 12:00
* @Version 1.0
**/
@Slf4j
@RestController
@RequestMapping("/h5")
public class H5ComboController {
@Resource
private SysOrderService orderService;
@Resource
private ComboService comboService;
/*@Resource
private WxUserService wxUserService;*/
@Autowired
@Qualifier("kryoRedisTemplate")
private RedisTemplate<String,Object> redisTemplate;
@Resource
private SubHealthDietaryRegulationMapper regulationMapper;
/* @GetMapping("/getCombos")
public List<ComboVO> getCombos(HttpServletRequest request, @RequestParam("deviceId") String deviceId, @RequestParam("phoneNumber") String phoneNumber,
@RequestParam("code") String code){
validatePhoneCode(phoneNumber,code);
WxUser one = wxUserService.getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getPhoneNumber, phoneNumber));
if (Objects.isNull(one)){
throw new ServiceException("您不是吉客中医微信小程序的用户");
}
List<WxOrder> wxOrders = orderService.getBaseMapper().selectList(new LambdaQueryWrapper<WxOrder>()
.eq(WxOrder::getOpenid, one.getOpenid())
.eq(WxOrder::getDeviceId, deviceId));
if (CollectionUtils.isEmpty(wxOrders)){
throw new ServiceException("抱歉,您在此设备上没有购买套餐");
}
String encrypt = phoneNumber;
try {
encrypt = AESUtil.encrypt(phoneNumber);
} catch (Exception e) {
log.error("手机号加密异常",e);
}
request.getSession().setAttribute("session",encrypt);
List<ComboVO> list = new ArrayList<>();
for (WxOrder wxOrder : wxOrders) {
Combo combo = comboService.getById(wxOrder.getComboId());
ComboVO vo = new ComboVO();
BeanUtils.copyProperties(combo,vo);
vo.setOrderId(wxOrder.getOrderId());
list.add(vo);
}
return list;
}
@GetMapping("/sendPhoneCode")
public ResultData sendPhoneCode(@RequestParam("phoneNumber") String phoneNumber){
String code = RandomStringUtils.randomNumeric(6);
try {
String s = SendSmsUtil.sendPhoneCode(phoneNumber, code);
JSONObject res = JSONObject.parseObject(s);
Integer statusCode = res.getInteger("statusCode");
if (statusCode != null && statusCode == 200){
redisTemplate.opsForValue().set("h5:"+phoneNumber,code,300, TimeUnit.SECONDS);
return ResultData.success();
}
return ResultData.failure(500,"验证码发送失败");
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
throw new ServiceException("验证码发送失败");
}
}
@GetMapping("/writeOff")
public boolean writeOff(HttpServletRequest request,@RequestParam("orderId") String orderId){
String encrypt = (String) request.getSession().getAttribute("session");
if (StringUtils.isBlank(encrypt)){
throw new ServiceException("No permission");
}
String decrypt = encrypt;
try {
decrypt = AESUtil.decrypt(encrypt);
} catch (Exception e) {
log.error("手机号解密异常",e);
}
WxOrder wxOrder = orderService.getOrderByIdAndPhone(orderId, decrypt);
if (Objects.isNull(wxOrder)){
throw new ServiceException("No permission");
}
wxOrder.setStatus(OrderStatus.verified.name());
return orderService.updateById(wxOrder);
}
@GetMapping("/getAuthQrcode")
public void getAuthQrcode(HttpServletResponse response){
// QRcodeUtil.generateQrcode(response);
}
private void validatePhoneCode(String phone,String code){
String cacheCode = (String) redisTemplate.opsForValue().get("h5:"+phone);
if (StringUtils.isBlank(cacheCode) || !cacheCode.equals(code)){
throw new ServiceException("验证码错误");
}
}
@GetMapping("/checkSubHealth")
public String checkSub(){
List<String> num = new ArrayList<>();
int i = 1;
while (i < 123){
num.add(String.valueOf(i));
i++;
}
for (String s : num) {
SubHealthDietaryRegulation regulation = regulationMapper.selectById(s);
if (Objects.isNull(regulation)){
return s;
}
}
return "none";
}*/
}
@@ -0,0 +1,80 @@
package cn.unizk.controller;
import cn.unizk.constant.VideoUploadConstants;
import cn.unizk.entity.po.Course;
import cn.unizk.entity.vo.CourseVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.exception.ServiceException;
import cn.unizk.service.HuaWeiYunService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
* @Author hao
* @Date 2026/3/6 15:09
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin/obs")
@Slf4j
public class HuaWeiYunController {
@Resource
private HuaWeiYunService huaWeiYunService;
/**
* 自定义场景生成临时url文档地址:https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0901.html#obs_21_0901__section11191986541
* @param file
* @param objName
* @return
*/
@PostMapping("/uploadVideo")
public ResultData uploadVideo(@RequestParam("file")MultipartFile file,@RequestParam("objName") String objName
){
if (file == null || file.isEmpty()){
throw new ServiceException("上传文件不能为空!");
}
String originalFilename = file.getOriginalFilename();
if (originalFilename == null){
throw new ServiceException("无法获取文件名!");
}
if (StringUtils.isBlank(objName)){
throw new ServiceException("objName cannot be null");
}
String extension = "";
int dotIndex = originalFilename.lastIndexOf(".");
if (dotIndex > 0) {
extension = originalFilename.substring(dotIndex + 1).toLowerCase();
}
if (!"mp4".equals(extension)) {
throw new ServiceException("不支持的文件格式: " + extension + ",仅支持 .mp4");
}
String contentType = file.getContentType();
if (contentType == null || !isAllowedMimeType(contentType)) {
throw new ServiceException("文件内容类型不合法:"+contentType);
}
long maxSize = 500 * 1024 * 1024;
if (file.getSize() > maxSize) {
throw new ServiceException("文件大小超过最大限制500MB");
}
String objectKey = objName + "/" + originalFilename;
String s = huaWeiYunService.uploadVideo(file, objectKey);
ResultData resultData = new ResultData();
resultData.setData(s);
resultData.setCode(200);
return resultData;
}
private boolean isAllowedMimeType(String contentType) {
// 标准化 contentType (去除 charset 等参数)
String mainType = contentType.split(";")[0].trim().toLowerCase();
return VideoUploadConstants.ALLOWED_MIME_TYPES.contains(mainType);
}
}
@@ -0,0 +1,220 @@
package cn.unizk.controller;
import cn.hutool.core.date.DateUtil;
import cn.unizk.entity.dto.IsoDTO;
import cn.unizk.entity.po.Device;
import cn.unizk.mapper.DeviceMapper;
import cn.unizk.mapper.SysDictMapper;
import cn.unizk.utils.AESUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @Author hao
* @Date 2025/10/20 18:21
* @Version 1.0
**/
@RestController
@RequestMapping("/")
@Slf4j
public class IsoController {
@Resource
private DeviceMapper deviceMapper;
@Resource
private SysDictMapper sysDictMapper;
@RequestMapping("/satoken/ajaxlogin")
public IsoDTO test() {
log.info("satoken----ajaxlogin---ajaxlogin-ajaxlogin---ajaxlogin");
IsoDTO dto = new IsoDTO();
dto.setRescode(200);
JSONObject data = new JSONObject();
JSONObject o = new JSONObject();
JSONObject jsonObject = new JSONObject();
jsonObject.put("yhid", "yhid");
jsonObject.put("yhxm", "yhxm");
jsonObject.put("yhzh", "yhzh");
o.put("userBase", jsonObject);
data.put("user", o);
data.put("tokenname", "abcde");
data.put("tokenvalue", "eyJhbGciOiJIUzI1NiJ9");
data.put("tokentimeout", 10000000);
dto.setData(data);
return dto;
}
/* @RequestMapping("/manage/device/submitputstore")
public IsoDTO isoDTO(@RequestParam(value = "un",required = false) String un,
@RequestParam(value = "mu",required = false) String mu,
@RequestParam(value = "hw",required = false) String hw,
@RequestParam(value = "sw",required = false) String sw,
@RequestParam(value = "bt",required = false) String bt){
log.info("un={};mu={};hw={};sw={};bt={}",un,mu,hw,sw,bt);
Device one = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, mu));
String deviceId;
if (Objects.isNull(one)){
int year = DateUtil.year(new Date());
String s = String.valueOf(year);
String substring = s.substring(2);
Integer count = deviceMapper.selectCurrentYearDeviceCount();
int i = count + 1;
deviceId = un + "-" + substring + "-" + i;
Device device = new Device();
device.setDeviceId(deviceId);
device.setMuid(mu);
device.setWarehousingTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceMapper.insert(device);
}else {
deviceId = one.getDeviceId();
}
IsoDTO dto = new IsoDTO();
JSONObject data = new JSONObject();
data.put("sbxlh",deviceId);
data.put("muid",mu);
dto.setData(data);
return dto;
}*/
/* @RequestMapping("/manage/device/submitputstore")
public IsoDTO isoDTO(@RequestParam(value = "un", required = false) String un,
@RequestParam(value = "mu", required = false) String mu,
@RequestParam(value = "hw", required = false) String hw,
@RequestParam(value = "sw", required = false) String sw,
@RequestParam(value = "bt", required = false) String bt) {
log.info("un={};mu={};hw={};sw={};bt={}", un, mu, hw, sw, bt);
Device one = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, mu));
String deviceId;
if (Objects.isNull(one)) {
int year = DateUtil.year(new Date());
String s = String.valueOf(year);
String substring = s.substring(2);
Integer count = deviceMapper.selectCurrentYearDeviceCount();
int i = count + 1;
String buqi = buqi(i);
deviceId = un + "-" + substring + "-" + buqi;
Device device = new Device();
device.setDeviceId(deviceId);
device.setMuid(mu);
device.setWarehousingTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceMapper.insert(device);
} else {
if (one.getDeviceId().length() < 5){
int year = DateUtil.year(new Date());
String s = String.valueOf(year);
String substring = s.substring(2);
Integer count = deviceMapper.selectCurrentYearDeviceCount();
int i = count + 1;
String buqi = buqi(i);
deviceId = un + "-" + substring + "-" + buqi;
deviceMapper.modifyDeviceId(deviceId,one.getDeviceId());
}else {
deviceId = one.getDeviceId();
}
}
IsoDTO dto = new IsoDTO();
JSONObject data = new JSONObject();
data.put("sbxlh", deviceId);
data.put("muid", mu);
dto.setData(data);
return dto;
}*/
@GetMapping("/equipment/warehousing")
public IsoDTO isoDTO2(HttpServletRequest request, @RequestParam(value = "un", required = false) String un,
@RequestParam(value = "mu", required = false) String mu) throws Exception {
log.info("mu={},un={}",mu,un);
String sKey = request.getHeader("s-key");
String decrypt = AESUtil.decrypt(sKey);
IsoDTO isoDTO = new IsoDTO();
if (!decrypt.equals(mu)){
isoDTO.setRescode(500);
isoDTO.setResmsg("数据校验不通过!");
isoDTO.setSuccess(false);
return isoDTO;
}
Device one = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, mu));
String deviceId;
if (Objects.isNull(one)){
int year = DateUtil.year(new Date());
String s = String.valueOf(year);
String substring = s.substring(2);
List<Device> deviceList = deviceMapper.selectCurrentYearDevice(substring);
int max;
if (CollectionUtils.isNotEmpty(deviceList)){
List<Integer> integerList = deviceList.stream()
.map(t -> {
String su = t.getDeviceId().substring(8);
String num = su.replaceFirst("^0+", "");
return Integer.parseInt(num);
})
.collect(Collectors.toList());
max = integerList.stream().mapToInt(Integer::intValue)
.max()
.orElseThrow(RuntimeException::new);
}else {
max = 0;
}
int fid = max + 1;
String buqi = buqi(fid);
deviceId = un + "-" + substring + "-" + buqi;
Device device = new Device();
device.setDeviceId(deviceId);
device.setMuid(mu);
device.setWarehousingTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceMapper.insert(device);
}else {
String oneDeviceId = one.getDeviceId();
String fs = oneDeviceId.substring(8);
if (!fs.startsWith("0")){
String buqi = buqi(Integer.parseInt(fs));
String substring = oneDeviceId.substring(5,7);
deviceId = un + "-" + substring + "-" + buqi;
deviceMapper.modifyDeviceId(deviceId,one.getDeviceId());
}else {
deviceId = one.getDeviceId();
}
}
IsoDTO dto = new IsoDTO();
JSONObject data = new JSONObject();
data.put("deviceId", deviceId);
data.put("muid", mu);
dto.setData(data);
dto.setRescode(200);
dto.setResmsg("success");
return dto;
}
private String buqi(int i) {
String str = String.valueOf(i);
int len = 5 - str.length();
String zero = "";
while (len > 0) {
zero += "0";
len--;
}
return zero + str;
}
}
@@ -0,0 +1,63 @@
package cn.unizk.controller;
import cn.unizk.entity.dto.IsoDTO;
import cn.unizk.entity.vo.SysUserVO;
import cn.unizk.entity.vo.SysUserVO2;
import cn.unizk.service.LoginService;
import cn.unizk.utils.HttpUtil;
import com.google.gson.Gson;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* @Author hao
* @Date 2025/8/22 11:32
* @Version 1.0
**/
@RequestMapping("/pc")
@RestController
public class LoginController {
@Resource
private LoginService loginService;
@PostMapping("/loginByUsername")
public SysUserVO loginByUsername(@RequestBody SysUserVO sysUserVO){
return loginService.loginByUsername(sysUserVO);
}
@PostMapping("/test")
public String test(@RequestParam("file") MultipartFile file){
/*byte[] fileHeader = new byte[16];
InputStream is = null;
try {
is = file.getInputStream();
int bytesRead = is.read(fileHeader);
is.close();
if (bytesRead < 4) {
throw new ServiceException("文件太小,无法识别");
}
FileMagicNumber magicNumber = FileMagicNumber.getMagicNumber(fileHeader);
return magicNumber.getMimeType();
} catch (IOException e) {
e.printStackTrace();
}
return null;*/
Map<String,String> params = new HashMap<>();
params.put("un","un");
params.put("mu","mu");
params.put("hw","hw");
params.put("sw","sw");
params.put("bt","bt");
String s = HttpUtil.doGet("http://localhost:8083/device/submitputstore", null, params);
Gson gson = new Gson();
IsoDTO isoDTO = gson.fromJson(s, IsoDTO.class);
System.out.println(isoDTO.toString());
return "success";
}
}
@@ -0,0 +1,36 @@
package cn.unizk.controller;
import cn.unizk.entity.po.Menu;
import cn.unizk.entity.vo.TreeMenu;
import cn.unizk.service.MenuService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author hao
* @Date 2025/8/26 11:29
* @Version 1.0
**/
@RestController
@RequestMapping("/pc")
public class MenuController {
@Resource
private MenuService menuService;
@GetMapping("/admin/menuList")
@PreAuthorize("hasRole('ADMIN')")
public List<TreeMenu> getAllMenus(){
return menuService.getAllMenus();
}
@GetMapping("/store/menuList")
public List<TreeMenu> getStoreMenus(){
return menuService.getStoreMenus();
}
}
@@ -0,0 +1,74 @@
package cn.unizk.controller;
import cn.unizk.entity.po.RunConsultation;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.RunConsultationMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @Author hao
* @Date 2026/7/15 11:33
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin/running")
public class RunningController {
@Resource
private RunConsultationMapper runConsultationMapper;
@PostMapping("/getConsults")
public Page<RunConsultation> page(@RequestBody UniPage<RunConsultation> uniPage){
Page<RunConsultation> page = (Page<RunConsultation>) uniPage.getPage();
LambdaQueryWrapper<RunConsultation> queryWrapper = uniPage.buildQueryWrapper(query -> {
LambdaQueryWrapper<RunConsultation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(query.getLesion()), RunConsultation::getLesion, query.getLesion());
return lambdaQueryWrapper;
});
return runConsultationMapper.selectPage(page,queryWrapper);
}
@PostMapping("/addOrUpdate")
public ResultData addOrUpdate(@RequestBody RunConsultation runConsultation){
List<String> msgs = new ArrayList<>();
if (StringUtils.isBlank(runConsultation.getLesion())){
msgs.add("lesion不能为空");
}
if (StringUtils.isBlank(runConsultation.getConclusion())){
msgs.add("conclusion不能为空");
}
if (StringUtils.isBlank(runConsultation.getQuestion())){
msgs.add("question不能为空");
}
if (StringUtils.isBlank(runConsultation.getPlan())){
msgs.add("plan不能为空");
}
if (CollectionUtils.isNotEmpty(msgs)){
String msg = String.join(";", msgs);
throw new ServiceException(msg);
}
if (runConsultation.getId() == null){
runConsultationMapper.insert(runConsultation);
}else {
runConsultationMapper.updateById(runConsultation);
}
return ResultData.success();
}
@PostMapping("/delete")
public ResultData delete(@RequestParam("id") Integer id){
runConsultationMapper.deleteById(id);
return ResultData.success();
}
}
@@ -0,0 +1,140 @@
package cn.unizk.controller;
import cn.hutool.core.date.DateUtil;
import cn.unizk.entity.dto.AppointmentDTO;
import cn.unizk.entity.po.DailySchedules;
import cn.unizk.entity.po.JkPhysio;
import cn.unizk.entity.po.MobileAppointments;
import cn.unizk.entity.vo.DailySchedulesVO;
import cn.unizk.entity.vo.RosterVO;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.mapper.DailySchedulesMapper;
import cn.unizk.mapper.JkPhysioMapper;
import cn.unizk.mapper.MobileAppointmentsMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @Author hao
* @Date 2026/9/15 11:37
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin/appointment")
public class SysAppointmentController {
@Resource
private DailySchedulesMapper dailySchedulesMapper;
@Resource
private JkPhysioMapper jkPhysioMapper;
@Resource
private MobileAppointmentsMapper mobileAppointmentsMapper;
@GetMapping("/roster")
public RosterVO roster(@RequestParam("physioId") Integer physioId){
/*List<DailySchedulesVO> dailySchedulesVOS = dailySchedulesMapper.selectRosterList(physioId);
dailySchedulesVOS.forEach(vo -> vo.setScheduleDateStr(vo.getScheduleDate().toString()));
dailySchedulesVOS.sort(((o1, o2) -> o1.getScheduleDate().compareTo(o2.getScheduleDate())));
Map<String, List<DailySchedulesVO>> collect = dailySchedulesVOS.stream()
.collect(Collectors.groupingBy(
DailySchedulesVO::getScheduleDateStr,
LinkedHashMap::new,
Collectors.toList()
));
Map<String, List<DailySchedulesVO>> rosterVO = new LinkedHashMap<>();
for (Map.Entry<String, List<DailySchedulesVO>> entry : collect.entrySet()) {
String s = DateUtil.dayOfWeekEnum(DateUtil.parseDate(entry.getKey())).toChinese();
rosterVO.put(entry.getKey() + " " + s,entry.getValue());
}
return rosterVO;*/
List<DailySchedulesVO> list = dailySchedulesMapper.selectRosterList(physioId);
list.forEach(vo -> vo.setScheduleDateStr(vo.getScheduleDate().toString()));
// 2) 全局日期列(去重 + 排序)
List<RosterVO.DateColumnVO> dates = list.stream()
.map(DailySchedulesVO::getScheduleDate)
.distinct()
.sorted()
.map(d -> {
RosterVO.DateColumnVO c = new RosterVO.DateColumnVO();
c.setDate(d.toString());
c.setWeekday(DateUtil.dayOfWeekEnum(DateUtil.parseDate(d.toString())).toChinese());
c.setLabel(d + " " + c.getWeekday());
return c;
})
.collect(Collectors.toList());
// 3) 全局时段行(去重 + 按开始时间排序,保证所有日期行序一致)
List<RosterVO.SlotVO> slots = list.stream()
.collect(Collectors.toMap(
DailySchedulesVO::getSlotCode,
vo -> new RosterVO.SlotVO(vo.getSlotCode(), vo.getSlotName(), vo.getStartTime(), vo.getEndTime()),
(a, b) -> a,
LinkedHashMap::new))
.values().stream()
.sorted(Comparator.comparing(RosterVO.SlotVO::getStartTime))
.collect(Collectors.toList());
// 建立索引:date + slotCode -> VO
Map<String, DailySchedulesVO> index = list.stream()
.collect(Collectors.toMap(
vo -> vo.getScheduleDateStr() + "|" + vo.getSlotCode(),
Function.identity(),
(a, b) -> a));
List<RosterVO.RosterRowVO> rows = new ArrayList<>();
for (RosterVO.SlotVO slot : slots) {
RosterVO.RosterRowVO row = new RosterVO.RosterRowVO();
row.setSlot(slot);
List<RosterVO.RosterCellVO> cells = new ArrayList<>();
for (RosterVO.DateColumnVO date : dates) {
DailySchedulesVO vo = index.get(date.getDate() + "|" + slot.getSlotCode());
RosterVO.RosterCellVO cell = new RosterVO.RosterCellVO();
cell.setDate(date.getDate());
if (vo == null) {
cell.setStatus("not_scheduled"); // 未排班
} else {
cell.setId(vo.getId());
cell.setStatus(vo.getStatus());
cell.setMaxBookings(vo.getMaxBookings());
cell.setBookedCount(vo.getBookedCount());
cell.setAvailableCount(vo.getAvailableCount());
}
cells.add(cell);
}
row.setCells(cells);
rows.add(row);
}
RosterVO vo = new RosterVO();
vo.setDates(dates);
vo.setSlots(slots);
vo.setRows(rows);
return vo;
}
@GetMapping("/getAllPhysio")
public List<JkPhysio> getAllPhysio(@RequestParam(value = "physioName",required = false) String physioName){
LambdaQueryWrapper<JkPhysio> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JkPhysio::getAudit,1).eq(JkPhysio::getStatus,"启用");
if (StringUtils.isNotBlank(physioName)){
wrapper.eq(JkPhysio::getPhysioName,physioName);
}
return jkPhysioMapper.selectList(wrapper);
}
@PostMapping("/appointmentList")
public Page<MobileAppointments> page(@RequestBody UniPage<AppointmentDTO> uniPage){
return null;
}
}
@@ -0,0 +1,101 @@
package cn.unizk.controller;
import cn.hutool.core.date.DateUtil;
import cn.unizk.entity.po.BusinessChannel;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.service.BusinessChannelService;
import cn.unizk.service.WxAuthService;
import cn.unizk.utils.HttpUtil;
import cn.unizk.utils.ServletUtils;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/10/9 14:33
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/channel")
@Slf4j
public class SysBusinessChannelController {
@Resource
private BusinessChannelService businessChannelService;
@Resource
private WxAuthService wxAuthService;
@PostMapping("/page")
public Page<BusinessChannel> page(@RequestBody UniPage<BusinessChannel> page){
Page<BusinessChannel> pagePage = (Page<BusinessChannel>) page.getPage();
LambdaQueryWrapper<BusinessChannel> queryWrapper = page.buildQueryWrapper(wrapper -> {
LambdaQueryWrapper<BusinessChannel> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(wrapper.getChannelName()), BusinessChannel::getChannelName, wrapper.getChannelName());
return lambdaQueryWrapper;
});
return businessChannelService.page(pagePage,queryWrapper);
}
@PostMapping("/delete")
public boolean delete(@RequestParam("channelId") Integer channelId){
return businessChannelService.removeById(channelId);
}
@PostMapping("/addChannel")
public boolean addChannel(@RequestBody BusinessChannel channel){
List<BusinessChannel> channelList = businessChannelService.list(new LambdaQueryWrapper<BusinessChannel>().eq(BusinessChannel::getChannelName, channel.getChannelName()));
if (CollectionUtils.isNotEmpty(channelList)){
throw new ServiceException(String.format("渠道商名称:%s已存在",channel.getChannelName()));
}
return businessChannelService.save(channel);
}
@GetMapping("/getAppletQrCode")
public void getAppletQrCode(HttpServletResponse response,@RequestParam(value = "channelId",required = false) Integer channelId,@RequestParam(value = "type",required = false) String type){
String accessToken = wxAuthService.getAccessToken();
String url = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s",accessToken);
log.info("url={}",url);
JSONObject jsonBody = JSONObject.of();
jsonBody.put("scene",channelId);
// jsonBody.put("check_path",false);
// jsonBody.put("env_version","trial");
jsonBody.put("width",280);
byte[] bytes = HttpUtil.postRtnBytes(url, null, jsonBody);
if (bytes == null || bytes.length == 0){
ServletUtils.renderString(response,"qrcode res is empty");
return;
}
String fileName = "";
if (channelId != null){
BusinessChannel businessChannel = businessChannelService.getById(channelId);
fileName = Objects.nonNull(businessChannel) ? businessChannel.getChannelName() : "";
}
// response.setContentType("image/jpeg");
response.setContentLength(bytes.length);
if ("download".equals(type)){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
response.setHeader("Content-Disposition", "attachment; filename=渠道商-"+channelId+now+".png");
}
try (OutputStream os = response.getOutputStream()) {
os.write(bytes);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,73 @@
package cn.unizk.controller;
import cn.unizk.entity.po.Combo;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.service.ComboService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @Author hao
* @Date 2025/9/18 10:42
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/combo")
public class SysComboController {
@Resource
private ComboService comboService;
@PostMapping("/comboPage")
@PreAuthorize("hasRole('ADMIN')")
public Page<Combo> page(@RequestBody UniPage<Combo> uniPage){
Page<Combo> page = (Page<Combo>) uniPage.getPage();
LambdaQueryWrapper<Combo> lambdaQueryWrapper = uniPage.buildQueryWrapper(query -> {
LambdaQueryWrapper<Combo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(query.getDeviceId()), Combo::getDeviceId, query.getDeviceId())
.like(StringUtils.isNotBlank(query.getComboName()), Combo::getComboName, query.getComboName());
return wrapper;
});
return comboService.page(page,lambdaQueryWrapper);
}
@PostMapping("/editCombo")
@PreAuthorize("hasRole('ADMIN')")
public boolean editCombo(@RequestBody Combo combo){
return comboService.updateById(combo);
}
@PostMapping("/addCombo")
@PreAuthorize("hasRole('ADMIN')")
public boolean addCombo(@RequestBody Combo combo){
if (StringUtils.isNotBlank(combo.getDeviceId()) && combo.getDeviceId().contains(",")){
String[] split = combo.getDeviceId().split(",");
List<Combo> list = new ArrayList<>();
for (String s : split) {
Combo c = new Combo();
c.setChannelId(combo.getChannelId());
c.setComboDuration(combo.getComboDuration());
c.setComboName(combo.getComboName());
c.setComboPrice(combo.getComboPrice());
c.setDeviceId(s);
c.setImage(combo.getImage());
list.add(c);
}
return comboService.saveBatch(list);
}
return comboService.save(combo);
}
@PostMapping("/delete")
@PreAuthorize("hasRole('ADMIN')")
public boolean delete(@RequestParam("comboId") Integer comboId){
return comboService.removeById(comboId);
}
}
@@ -0,0 +1,114 @@
package cn.unizk.controller;
import cn.unizk.entity.po.Chapter;
import cn.unizk.entity.po.Course;
import cn.unizk.entity.po.MobileUserVideoProgress;
import cn.unizk.entity.vo.CourseVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.ChapterMapper;
import cn.unizk.mapper.CourseMapper;
import cn.unizk.mapper.MobileUserVideoProgressMapper;
import cn.unizk.utils.BeanCopyUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
* @Author hao
* @Date 2026/3/9 11:17
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin/course")
@Slf4j
public class SysCourseController {
private static final String OSS_DOMAIN = "http://oss.peaktcm.com/";
@Resource
private CourseMapper courseMapper;
@Resource
private ChapterMapper chapterMapper;
/*@Resource
private WxUserVideoProgressMapper wxUserVideoProgressMapper;*/
@Resource
private MobileUserVideoProgressMapper mobileUserVideoProgressMapper;
@PostMapping("/addCourse")
public ResultData addCourse(@Validated @RequestBody Course course){
course.setCreateTime(LocalDateTime.now());
return courseMapper.insert(course) > 0 ? ResultData.success() : ResultData.failure(500,"新增课程失败");
}
@PostMapping("/addChapter")
public ResultData addChapter(@Validated @RequestBody Chapter chapter){
chapter.setCreateTime(LocalDateTime.now());
chapter.setVideoUrl(OSS_DOMAIN + chapter.getVideoUrl());
return chapterMapper.insert(chapter) > 0 ? ResultData.success() : ResultData.failure(500,"新增章节失败");
}
@PostMapping("/courseList")
public Page<CourseVO> coursePage(@RequestBody UniPage<Course> uniPage){
Page<Course> page = (Page<Course>) uniPage.getPage();
LambdaQueryWrapper<Course> buildQueryWrapper = uniPage.buildQueryWrapper(wrapper -> {
LambdaQueryWrapper<Course> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(wrapper.getTitle()), Course::getTitle, wrapper.getTitle());
return queryWrapper;
});
Page<Course> coursePage = courseMapper.selectPage(page, buildQueryWrapper);
Page<CourseVO> voPage = new Page<>(page.getCurrent(),page.getSize());
List<Course> records = coursePage.getRecords();
if (CollectionUtils.isEmpty(records)){
return new Page<>();
}
Collection<CourseVO> courseVOS = BeanCopyUtils.copyArrays(records, CourseVO::new);
for (CourseVO courseVO : courseVOS) {
List<Chapter> chapters = chapterMapper.selectList(new LambdaQueryWrapper<Chapter>().eq(Chapter::getCourseId, courseVO.getCourseId()));
courseVO.setChapterTotal(chapters.size());
}
voPage.setRecords((List<CourseVO>) courseVOS);
voPage.setTotal(coursePage.getTotal());
return voPage;
}
@PostMapping("/chapterList")
public Page<Chapter> chapterPage(@RequestBody UniPage<Chapter> uniPage){
Page<Chapter> page = (Page<Chapter>) uniPage.getPage();
LambdaQueryWrapper<Chapter> queryWrapper = uniPage.buildQueryWrapper(wr -> {
if (wr.getCourseId() == null){
throw new ServiceException("courseId必传");
}
LambdaQueryWrapper<Chapter> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Chapter::getCourseId,wr.getCourseId()).like(StringUtils.isNotBlank(wr.getTitle()), Chapter::getTitle, wr.getTitle());
return wrapper;
});
return chapterMapper.selectPage(page,queryWrapper);
}
@PostMapping("/deleteCourse")
public ResultData deleteCourse(@RequestParam("courseId") Integer courseId){
List<Chapter> chapters = chapterMapper.selectList(new LambdaQueryWrapper<Chapter>().eq(Chapter::getCourseId, courseId));
if (CollectionUtils.isNotEmpty(chapters)){
throw new ServiceException("此课程有内容,请先删除课程章节");
}
return courseMapper.deleteById(courseId) > 0 ? ResultData.success() : ResultData.failure();
}
@PostMapping("/deleteChapter")
public ResultData deleteChapter(@RequestParam("chapterId") Integer chapterId){
mobileUserVideoProgressMapper.delete(new LambdaQueryWrapper<MobileUserVideoProgress>().eq(MobileUserVideoProgress::getChapterId,chapterId));
mobileUserVideoProgressMapper.delete(new LambdaQueryWrapper<MobileUserVideoProgress>().eq(MobileUserVideoProgress::getChapterId,chapterId));
return chapterMapper.deleteById(chapterId) > 0 ? ResultData.success() : ResultData.failure();
}
}
@@ -0,0 +1,669 @@
package cn.unizk.controller;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.unizk.constants.Constants;
import cn.unizk.entity.dto.DeviceExcelDTO;
import cn.unizk.entity.po.*;
import cn.unizk.entity.vo.*;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.*;
import cn.unizk.service.*;
import cn.unizk.utils.BeanCopyUtils;
import cn.unizk.utils.QRcodeUtil;
import cn.unizk.utils.XorUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.batch.MybatisBatch;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @Author hao
* @Date 2025/9/12 10:46
* @Version 1.0
**/
@RestController
@Slf4j
@RequestMapping("/pc/admin/device")
public class SysDeviceController {
@Resource
private DeviceService deviceService;
@Resource
private UdpClientService udpClientService;
@Resource
private UdpServerService2 udpServerService;
@Resource
private StoreService storeService;
@Resource
private SqlSessionFactory sqlSessionFactory;
/*@Resource
private WxUserService wxUserService;*/
@Resource
private MobileAppUserMapper mobileAppUserMapper;
@Resource
private ComboService comboService;
@Resource
private Executor udpRetryExecutor;
@Resource
private DeviceUsageRecordMapper deviceUsageRecordMapper;
@Resource
private DeviceRechargeRecordMapper rechargeRecordMapper;
@Resource
private DeviceUsageDetailMapper usageDetailMapper;
@Resource
private DeviceUpgradeMapper upgradeMapper;
@Resource
private WxAuthService wxAuthService;
@Resource
private TreatParameterMapper treatParameterMapper;
private static final Path UPLOAD = Paths.get(Constants.DEVICE_UPGRADE_PACKAGE_ROOT_PATH);
private static final String SAFE_FILENAME_REGEX = "^[\\w\\u4e00-\\u9fa5.-]+$";
private static String power_on_off_command = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
private static final String OPEN01 = "$01|MU{uid}|DT{date}|ON{1}|CF1|WM1|TC000|*{CRC}\r\n";
@PostMapping("/onResponse")
public boolean onResponse(@RequestParam("deviceId") String deviceId){
return true;
}
@PostMapping("/closeOrOpen")
public boolean closeOrOpen(@RequestParam("deviceId") String deviceId,
@RequestParam("status") String status){
Device device = deviceService.getById(deviceId);
if (Objects.isNull(device)){
throw new ServiceException("设备不存在");
}
if ("1".equals(status) && device.getSurplus() < 1){
throw new ServiceException("此设备无剩余使用次数,无法启用,请先充值!");
}
UdpServerService2.DeviceInfo deviceInfo = udpServerService.getDeviceInfo(device.getMuid());
if (Objects.isNull(deviceInfo)){
device.setStatus(status);
return deviceService.updateById(device);
}
udpRetryExecutor.execute(() -> retryUdp(device, status));
return true;
/*Device device = deviceService.getById(deviceId);
device.setStatus(status);
deviceService.updateById(device);
//发送UDP协议
UdpServerService.DeviceInfo deviceInfo = udpServerService.getDeviceInfo(device.getMuid());
if (Objects.isNull(deviceInfo)){
log.info("deviceInfo is null+++++++");
return false;
}
if ("1".equals(deviceInfo.getStatus())){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = OPEN01.replace("{uid}", device.getMuid()).replace("{date}", now).replace("{1}", status);
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String msg = replace.replace("{CRC}", asciiHex);
log.info("传输的数据是:{}",msg);
return udpClientService.sendCommand(deviceInfo,msg);
}
if ("2".equals(deviceInfo.getStatus())){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
power_on_off_command = power_on_off_command.replace("{uid}",device.getMuid())
.replace("{date}",now).replace("{1}",status).replace("{2}","000");
String crc = power_on_off_command.substring(1, power_on_off_command.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
power_on_off_command = power_on_off_command.replace("{CRC}",asciiHex);
log.info("传输的数据是:{}",power_on_off_command);
return udpClientService.sendCommand(deviceInfo,power_on_off_command);
}
return false;*/
}
private void retryUdp(Device device, String status) {
int left = 10;
while (left-- > 0) {
if (udpServerService.reply(device, status)) {
return;
}
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
log.warn("UDP 回复重试耗尽,deviceId={}", device.getDeviceId());
}
@PostMapping("/openStatus")
public boolean openStatus(@RequestParam("deviceId") String deviceId,
@RequestParam("status") String status){
Device device = deviceService.getById(deviceId);
if (Objects.isNull(device)){
throw new ServiceException("设备不存在");
}
device.setStatus(status);
deviceService.updateById(device);
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = OPEN01.replace("{uid}", device.getMuid()).replace("{date}", now).replace("{1}", status);
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String msg = replace.replace("{CRC}", asciiHex);
UdpServerService2.DeviceInfo deviceInfo = udpServerService.getDeviceInfo(device.getMuid());
// return udpClientService.sendCommand(deviceInfo,msg);
return true;
}
@PostMapping("/list")
public Page<Device> list(@RequestBody UniPage<DeviceVO> uniPage){
return deviceService.listPage(uniPage);
}
@PostMapping("/exportDevices")
public void exportDevices(@RequestBody UniPage<DeviceVO> uniPage,HttpServletResponse response){
List<Device> records;
uniPage.setPageSize(-1);
DeviceVO queryBody = uniPage.getQueryBody();
if (CollectionUtils.isEmpty(queryBody.getDeviceIds())){
Page<Device> page = deviceService.listPage(uniPage);
records = page.getRecords();
}else {
records = deviceService.listByIds(queryBody.getDeviceIds());
}
Collection<DeviceExcelDTO> deviceExcelDTOS = BeanCopyUtils.copyArrays(records, DeviceExcelDTO::new);
deviceExcelDTOS.forEach(deviceExcelDTO -> {
if (deviceExcelDTO.getOutboundTime() == null){
deviceExcelDTO.setOutboundType("未出库");
}else {
deviceExcelDTO.setOutboundType("已出库");
}
});
try {
String fileName = URLEncoder.encode("设备信息", "UTF-8").replaceAll("\\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.addHeader("Content-disposition","attachment;filename="+fileName+".xlsx");
EasyExcel.write(response.getOutputStream(),DeviceExcelDTO.class).sheet("设备信息").doWrite(deviceExcelDTOS);
} catch (Exception e) {
throw new ServiceException("服务异常");
}
}
@PostMapping("/remark")
public boolean remark(@RequestBody Device device){
Device dv = new Device();
dv.setDeviceId(device.getDeviceId());
String remark = device.getRemark();
if (remark == null){
remark = "";
}
dv.setRemark(remark);
return deviceService.updateById(dv);
}
@PostMapping("/outbound")
public ResultData outbound(@RequestBody DeviceOutboundVO vo){
if (vo.getStoreId() == null && StringUtils.isBlank(vo.getPhoneNumber())){
throw new ServiceException("请输入门店id或个人手机号");
}
List<String> deviceIds = vo.getDeviceIds();
List<Device> devices = deviceService.listByIds(deviceIds);
boolean b = devices.stream().allMatch(device -> device.getOutboundTime() == null);
if (!b){
throw new ServiceException("该批次出库设备中存在已出库的设备!");
}
if (vo.getStoreId() != null){
JkStore jkStore = storeService.getById(vo.getStoreId());
if (Objects.isNull(jkStore)){
throw new ServiceException("查无此店!");
}
//出库到门店
devices.forEach(device -> {
device.setStoreId(vo.getStoreId());
device.setAffiliatedWith(jkStore.getStoreName());
device.setOutboundTime(LocalDateTime.now(Clock.systemDefaultZone()));
});
// batchUpdate(devices);
deviceService.updateBatchById(devices);
}else if (StringUtils.isNotBlank(vo.getPhoneNumber())){
//出库到个人
MobileAppUser wxUser = mobileAppUserMapper.selectOne(new LambdaQueryWrapper<MobileAppUser>().eq(MobileAppUser::getPhoneNumber, vo.getPhoneNumber()));
if (Objects.isNull(wxUser)){
throw new ServiceException("该手机号未注册!");
}
devices.forEach(device -> {
device.setUserId(wxUser.getId());
device.setAffiliatedWith(wxUser.getNickname());
device.setOutboundTime(LocalDateTime.now());
});
// batchUpdate(devices);
deviceService.updateBatchById(devices);
}else if (vo.getAgentId() != null){
//出库到代理商
log.info("出库到代理商--------");
}
return ResultData.success();
}
@GetMapping("/getOne")
public Device getOne(@RequestParam("deviceId") String deviceId){
Device device = deviceService.getById(deviceId);
if (Objects.isNull(device)){
throw new ServiceException("设备不存在");
}
return device;
}
@GetMapping("/getQRCode")
public void downloadQRcode(HttpServletResponse response,@RequestParam("deviceId") String deviceId,
@RequestParam(value = "optionType",required = false) String optionType){
QRcodeUtil.getDeviceQrCode(response,deviceId,optionType);
}
private void batchUpdate(List<Device> devices){
MybatisBatch<Device> mybatisBatch = new MybatisBatch<>(sqlSessionFactory,devices);
MybatisBatch.Method<Device> method = new MybatisBatch.Method<>(DeviceMapper.class);
mybatisBatch.execute(method.insert());
}
@GetMapping("/getCztc")
public List<Combo> getCztc(){
return comboService.list();
}
@PostMapping("/cz")
public boolean cz(@RequestBody Device device){
Device deviceServiceById = deviceService.getById(device.getDeviceId());
deviceServiceById.setBalance(device.getBalance());
return deviceService.updateById(deviceServiceById);
}
@GetMapping("/getDeviceByChannel")
public List<Device> getDeviceByChannel(@RequestParam(value = "channelId",required = false) Integer channelId,@RequestParam(value = "deviceId",required = false) String deviceId){
if (channelId != null){
List<JkStore> list = storeService.list(new LambdaQueryWrapper<JkStore>().eq(JkStore::getChannelId, channelId));
List<Integer> storeIds = list.stream().map(JkStore::getId).collect(Collectors.toList());
if (CollectionUtils.isEmpty(storeIds)){
return new ArrayList<>();
}
return deviceService.list(new LambdaQueryWrapper<Device>().in(Device::getStoreId,storeIds).like(StringUtils.isNotBlank(deviceId),Device::getDeviceId,deviceId));
}
return deviceService.list();
}
@PostMapping("/recharge")
@Transactional(rollbackFor = Exception.class)
public boolean recharge(@RequestBody Device device){
Device deviceServiceById = deviceService.getById(device.getDeviceId());
// deviceServiceById.setPaymentMethod(device.getPaymentMethod());
Integer surplus = deviceServiceById.getSurplus();
// deviceServiceById.setSurplus(surplus + device.getSurplus());
deviceServiceById.setBalance(deviceServiceById.getBalance().add(device.getBalance()));
if (device.getPaymentMethod() == 1){
if (deviceServiceById.getPaymentMethod() != null && deviceServiceById.getPaymentMethod() == 1){
DateTime dateTime = DateUtil.offsetDay(new Date(), device.getSurplus() + surplus);
String s = DateUtil.formatDate(dateTime);
deviceServiceById.setExpireTime(DateUtil.formatDateTime(DateUtil.parseDate(s)));
deviceServiceById.setSurplus(surplus + device.getSurplus());
}else {
DateTime dateTime = DateUtil.offsetDay(new Date(), device.getSurplus());
String s = DateUtil.formatDate(dateTime);
deviceServiceById.setExpireTime(DateUtil.formatDateTime(DateUtil.parseDate(s)));
deviceServiceById.setSurplus(device.getSurplus());
}
}
if (device.getPaymentMethod() == 2){
if (deviceServiceById.getPaymentMethod() != null && deviceServiceById.getPaymentMethod() == 2){
deviceServiceById.setExpireTime(String.valueOf(device.getSurplus() + surplus));
deviceServiceById.setSurplus(device.getSurplus() + surplus);
}else {
deviceServiceById.setExpireTime(String.valueOf(device.getSurplus()));
deviceServiceById.setSurplus(device.getSurplus());
}
}
deviceServiceById.setRechargeCount(deviceServiceById.getRechargeCount() + 1);
deviceServiceById.setPaymentMethod(device.getPaymentMethod());
DeviceRechargeRecord rechargeRecord = new DeviceRechargeRecord();
rechargeRecord.setQuantity(device.getSurplus());
rechargeRecord.setDeviceId(device.getDeviceId());
rechargeRecord.setMoney(device.getBalance());
rechargeRecord.setRechargeType(device.getPaymentMethod());
rechargeRecord.setCreateTime(LocalDateTime.now());
rechargeRecordMapper.insert(rechargeRecord);
return deviceService.updateById(deviceServiceById);
}
@PostMapping("/rechargeRecord")
public Page<DeviceRechargeRecord> page(@RequestBody UniPage<DeviceRechargeRecord> uniPage){
Page<DeviceRechargeRecord> page = (Page<DeviceRechargeRecord>) uniPage.getPage();
DeviceRechargeRecord queryBody = uniPage.getQueryBody();
return rechargeRecordMapper.selectPage(page,new LambdaQueryWrapper<DeviceRechargeRecord>().eq(DeviceRechargeRecord::getDeviceId,queryBody.getDeviceId()));
}
@PostMapping("/usageRecord")
public Page<DeviceUsageRecordVO> usageRecord(@RequestBody UniPage<UsageDetailVO> uniPage){
Page<DeviceUsageRecord> page = new Page<>(uniPage.getCurrentPage(),uniPage.getPageSize());
UsageDetailVO queryBody = uniPage.getQueryBody();
LambdaQueryWrapper<DeviceUsageRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(queryBody.getDeviceId()),DeviceUsageRecord::getDeviceId,queryBody.getDeviceId());
if (queryBody.getStoreId() != null){
List<Device> devices = deviceService.list(new LambdaQueryWrapper<Device>().eq(Device::getStoreId, queryBody.getStoreId()));
List<String> deviceIds = devices.stream().map(Device::getDeviceId).collect(Collectors.toList());
wrapper.in(DeviceUsageRecord::getDeviceId,deviceIds);
}
if (queryBody.getStartTime() != null && queryBody.getEndTime() != null){
wrapper.between(DeviceUsageRecord::getStartTime,queryBody.getStartTime(),queryBody.getEndTime());
}
wrapper.and(StringUtils.isNotBlank(queryBody.getKeyword()),r -> {
r.eq(DeviceUsageRecord::getDeviceId,queryBody.getKeyword())
.or()
.eq(DeviceUsageRecord::getMuid,queryBody.getKeyword());
});
wrapper.orderByDesc(DeviceUsageRecord::getStartTime);
Page<DeviceUsageRecord> recordPage = deviceUsageRecordMapper.selectPage(page, wrapper);
List<DeviceUsageRecord> records = recordPage.getRecords();
List<DeviceUsageRecordVO> voList = new ArrayList<>();
for (DeviceUsageRecord record : records) {
DeviceUsageRecordVO vo = new DeviceUsageRecordVO();
BeanUtils.copyProperties(record,vo);
if (record.getStartTime() != null && record.getEndTime() != null){
long minutes = Duration.between(record.getStartTime(), record.getEndTime()).toMinutes();
vo.setUseDuration(minutes);
}
voList.add(vo);
}
Page<DeviceUsageRecordVO> voPage = new Page<>();
voPage.setRecords(voList);
voPage.setCurrent(recordPage.getCurrent());
voPage.setSize(recordPage.getSize());
voPage.setTotal(recordPage.getTotal());
return voPage;
}
@PostMapping("/changeConfig")
public boolean changeConfig(@RequestBody DeviceConfigVO vo){
List<String> deviceIds = vo.getDeviceIds();
if (CollectionUtils.isEmpty(deviceIds)){
return false;
}
List<Device> deviceList = new ArrayList<>();
deviceIds.forEach(id -> {
Device device = new Device();
device.setDeviceId(id);
device.setCf(vo.getCf());
device.setStatus(vo.getCanUse());
device.setUpgradeSwitch(vo.getUpgradeSwitch());
deviceList.add(device);
});
deviceService.updateBatchById(deviceList);
return true;
}
@PostMapping("/upgrade")
public ResultData upgrade(@RequestParam(value = "id",required = false) Integer id,
@RequestParam(value = "file",required = false) MultipartFile file,
@RequestParam String sVersion,
@RequestParam String hVersion,
@RequestParam String fileName,
@RequestParam(value = "releaseNotes",required = false) String releaseNotes,
@RequestParam Integer beCurrentVersion){
validateFile(id,file);
LambdaQueryWrapper<DeviceUpgrade> wrapper = new LambdaQueryWrapper<>();
if (id == null){
wrapper.eq(DeviceUpgrade::getHVersion, hVersion).eq(DeviceUpgrade::getSVersion, sVersion);
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(wrapper);
if (Objects.nonNull(deviceUpgrade)){
throw new ServiceException("已存在相同版本的硬件号和软件号");
}
if (beCurrentVersion == 1){
upgradeMapper.updateCurrentVersion();
}
createFile(file);
DeviceUpgrade upgrade = new DeviceUpgrade();
upgrade.setBeCurrentVersion(beCurrentVersion);
upgrade.setFileName(fileName);
upgrade.setHVersion(hVersion);
upgrade.setSVersion(sVersion);
upgrade.setReleaseNotes(releaseNotes);
upgrade.setCreateTime(LocalDateTime.now());
return ResultData.success(upgradeMapper.insert(upgrade));
}else {
wrapper.eq(DeviceUpgrade::getHVersion, hVersion).eq(DeviceUpgrade::getSVersion, sVersion).ne(DeviceUpgrade::getId,id);
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(wrapper);
if (Objects.nonNull(deviceUpgrade)){
throw new ServiceException("已存在相同版本的硬件号和软件号");
}
if (beCurrentVersion == 1){
upgradeMapper.updateCurrentVersion();
}
if (file != null && !file.isEmpty()){
DeviceUpgrade selectById = upgradeMapper.selectById(id);
if (Objects.nonNull(selectById)){
deleteFile(selectById.getFileName());
}
createFile(file);
}
DeviceUpgrade upgrade = new DeviceUpgrade();
upgrade.setId(id);
upgrade.setBeCurrentVersion(beCurrentVersion);
upgrade.setFileName(fileName);
upgrade.setHVersion(hVersion);
upgrade.setSVersion(sVersion);
upgrade.setReleaseNotes(releaseNotes);
upgrade.setUpdateTime(LocalDateTime.now());
return ResultData.success(upgradeMapper.updateById(upgrade));
}
}
private void createFile(MultipartFile file){
String originalFilename = file.getOriginalFilename();
try {
Files.createDirectories(UPLOAD);
Path dest = UPLOAD.resolve(originalFilename);
file.transferTo(dest);
} catch (IOException e) {
log.error("文件上传失败",e);
}
}
@PostMapping("/uploadUpgradeFile")
public ResultData uploadUpgradeFile(@RequestParam("file")MultipartFile file,@RequestParam(value = "id",required = false) Integer id) throws IOException {
if (file == null || file.isEmpty()){
throw new ServiceException("请上传文件");
}
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename) || originalFilename.endsWith(".bin")){
throw new ServiceException("文件类型不正确,请重新上传");
}
if (originalFilename.startsWith(".") || !Pattern.matches(SAFE_FILENAME_REGEX,originalFilename)){
throw new ServiceException("文件名不合法!");
}
// validateFile(id,originalFilename,true);
Files.createDirectories(UPLOAD);
Path dest = UPLOAD.resolve(originalFilename);
file.transferTo(dest);
ResultData<String> data = new ResultData<>();
data.setData(originalFilename);
data.setMessage("上传成功");
data.setCode(200);
return data;
}
@PostMapping("/revokeUpgradeFile")
public void revokeUpgradeFile(@RequestParam("filename") String filename){
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getFileName, filename));
if (Objects.nonNull(deviceUpgrade)){
return;
}
deleteFile(filename);
}
private void validateFile(Integer id,MultipartFile file){
if ((file == null || file.isEmpty()) && id == null){
throw new ServiceException("请上传文件");
}
if (file != null && !file.isEmpty()){
final long maxLen = 1024 * 32;
if (file.getSize() > maxLen){
throw new ServiceException("文件太大,请重新上传");
}
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename) || !originalFilename.endsWith(".bin")){
throw new ServiceException("文件类型不正确,请重新上传");
}
if (originalFilename.startsWith(".") || !Pattern.matches(SAFE_FILENAME_REGEX,originalFilename)){
throw new ServiceException("文件名不合法!");
}
}
if (id == null){
String originalFilename = file.getOriginalFilename();
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getFileName, originalFilename));
if (Objects.nonNull(deviceUpgrade)){
throw new ServiceException(String.format("已存在文件名为%s的文件!",originalFilename));
}
}else {
if (file != null && !file.isEmpty()){
String originalFilename = file.getOriginalFilename();
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getFileName, originalFilename).ne(DeviceUpgrade::getId, id));
if (Objects.nonNull(deviceUpgrade)){
throw new ServiceException(String.format("已存在文件名为%s的文件!",originalFilename));
}
DeviceUpgrade upgrade = upgradeMapper.selectById(id);
if (Objects.isNull(upgrade)){
throw new ServiceException(String.format("id为%d的文件不存在!",id));
}
}
/*if (isUpload){
deleteFile(upgrade.getFileName());
}*/
}
}
private void deleteFile(String filename){
Path path = Paths.get(Constants.DEVICE_UPGRADE_PACKAGE_ROOT_PATH + filename);
try {
Files.deleteIfExists(path);
} catch (IOException e) {
log.error("{}删除失败!",filename,e);
}
}
@PostMapping("/zeroClearing")
public boolean zeroClearing(@RequestBody List<String> deviceIds){
for (String deviceId : deviceIds) {
LambdaUpdateWrapper<Device> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Device::getDeviceId,deviceId)
.set(Device::getPaymentMethod,null)
.set(Device::getSurplus,0)
.set(Device::getExpireTime,"0");
deviceService.update(updateWrapper);
}
return true;
}
@PostMapping("/addDevice")
public void addDevice(@RequestBody Device device){
deviceService.save(device);
}
@GetMapping("/usageDetail")
public Page<DeviceUsageDetail> page(@RequestParam("usageRecordId") Integer usageRecordId,UniPage uniPage){
Page<DeviceUsageDetail> page = new Page<>(uniPage.getCurrentPage(),uniPage.getPageSize());
return usageDetailMapper.selectPage(page,new LambdaQueryWrapper<DeviceUsageDetail>().eq(DeviceUsageDetail::getUsageRecordId,usageRecordId));
}
@PostMapping("/reset")
public boolean reset(@RequestBody List<String> deviceIds){
if (CollectionUtils.isEmpty(deviceIds)){
return false;
}
return deviceService.lambdaUpdate()
.set(Device::getStoreId, null)
.set(Device::getAffiliatedWith, null)
.set(Device::getOutboundTime, null)
.set(Device::getExpireTime, null)
.set(Device::getSurplus, 0)
.set(Device::getBalance, 0)
.set(Device::getPaymentMethod, null)
.set(Device::getUserId, null)
.set(Device::getAgentId, null)
.set(Device::getForcePay,0)
.in(Device::getDeviceId, deviceIds)
.update();
}
@GetMapping("/software")
public Page<DeviceUpgrade> page(UniPage uniPage,@RequestParam(value = "keyword",required = false) String keyword){
Page<DeviceUpgrade> page = new Page<>(uniPage.getCurrentPage(),uniPage.getPageSize());
LambdaQueryWrapper<DeviceUpgrade> wrapper = new LambdaQueryWrapper<>();
wrapper.and(StringUtils.isNotBlank(keyword),r -> {
r.eq(DeviceUpgrade::getHVersion,keyword)
.or()
.eq(DeviceUpgrade::getSVersion,keyword);
});
return upgradeMapper.selectPage(page,wrapper);
}
@PostMapping("/deleteSoftware")
public Integer deleteSoftware(@RequestParam("id") Integer id){
DeviceUpgrade deviceUpgrade = upgradeMapper.selectById(id);
if (deviceUpgrade == null){
return 0;
}
deleteFile(deviceUpgrade.getFileName());
return upgradeMapper.deleteById(id);
}
@GetMapping("/getTopThree")
public ResultData<List<DeviceUsageDetailVO>> getTopThree(@RequestParam("usageRecordId") Integer usageRecordId){
List<DeviceUsageDetail> deviceUsageDetails = usageDetailMapper.selectTopThree(usageRecordId);
List<String> plList = deviceUsageDetails.stream().map(DeviceUsageDetail::getPl).distinct().collect(Collectors.toList());
List<DeviceUsageDetailVO> detailList = new ArrayList<>();
for (String s : plList) {
DeviceUsageDetail maxTime = deviceUsageDetails.stream().filter(detail -> detail.getPl().equals(s)).max((o1, o2) -> o1.getCreateTime().compareTo(o2.getCreateTime())).get();
DeviceUsageDetail minTime = deviceUsageDetails.stream().filter(detail -> detail.getPl().equals(s)).min((o1, o2) -> o1.getCreateTime().compareTo(o2.getCreateTime())).get();
long seconds = Duration.between(minTime.getCreateTime(), maxTime.getCreateTime()).getSeconds();
DeviceUsageDetailVO vo = new DeviceUsageDetailVO();
BeanUtils.copyProperties(maxTime,vo);
vo.setSustainTime(seconds);
detailList.add(vo);
}
MobileAppUser wxUser = mobileAppUserMapper.selectById(9);
TreatParameter treatParameter = treatParameterMapper.selectById(wxUser.getPainNumber());
detailList.forEach(detail -> {
detail.setUl(treatParameter.getUl());
detail.setPw(treatParameter.getPw());
detail.setFr(treatParameter.getFr());
// detail.setCreateTime(null);
});
detailList.sort((o1, o2) -> (int) (o2.getSustainTime() - o1.getSustainTime()));
return ResultData.success(detailList);
}
}
@@ -0,0 +1,58 @@
package cn.unizk.controller;
import cn.unizk.entity.po.SysFile;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.enums.FileType;
import cn.unizk.exception.ServiceException;
import cn.unizk.service.SysFileService;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
/**
* @Author hao
* @Date 2025/8/27 18:06
* @Version 1.0
**/
@RestController
@RequestMapping("/pc")
public class SysFileController {
@Resource
private SysFileService sysFileService;
@PostMapping("/uploadFile")
public ResultData uploadFile(@RequestParam("file") MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename) || originalFilename.lastIndexOf(".") == -1){
throw new ServiceException("文件格式不正确");
}
String fileType = originalFilename.substring(originalFilename.lastIndexOf("."));
Set<String> types = ImmutableSet.of(".png",".jpg",".jpeg",".ico");
/*if (!types.contains(fileType)){
throw new ServiceException("目前只支持上传png,jpg,jpeg,ico类型文件");
}*/
String bucketName = FileType.getMinioBucketName(file.getContentType()).getBucketName();
return sysFileService.simpleUpload(file, bucketName);
}
@PostMapping("/editFile")
public boolean editFile(){
List<SysFile> sysFiles = sysFileService.list();
sysFiles.forEach(sysFile -> {
String fileUrl = sysFile.getFileUrl();
String replace = fileUrl.replace("http://139.9.2.195:8002/", "https://peaktcm.com:8000/");
sysFile.setFileUrl(replace);
});
return sysFileService.updateBatchById(sysFiles);
}
}
@@ -0,0 +1,318 @@
package cn.unizk.controller;
import cn.unizk.entity.po.*;
import cn.unizk.entity.vo.PhysioExamVO;
import cn.unizk.entity.vo.PhysioVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.*;
import cn.unizk.poi.SubHealthReadListener;
import cn.unizk.service.PhysioService;
import cn.unizk.service.StoreService;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.File;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/9/9 14:45
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin")
public class SysPhysioController {
@Resource
private PhysioService physioService;
@Resource
private StoreService storeService;
@Resource
private ReportTemplateMapper reportTemplateMapper;
@Resource
private MobileAppUserMapper mobileAppUserMapper;
@Resource
private TreatParameterMapper treatParameterMapper;
@Resource
private SubHealthDietaryRegulationMapper subHealthDietaryRegulationMapper;
@Resource
private PhysioExamMapper examMapper;
@Autowired
@Qualifier("kryoRedisTemplate")
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_TOKEN_KEY = "jk-applet-";
private static final String CACHE_TOKEN_PREFIX = "mobile-t:";
@PostMapping("/listExamTitle")
public Page<PhysioExamVO> listExamTitle(@RequestBody UniPage<PhysioExam> uniPage ){
Page<PhysioExam> page = (Page<PhysioExam>) uniPage.getPage();
LambdaQueryWrapper<PhysioExam> lambdaQueryWrapper = uniPage.buildQueryWrapper(q -> {
LambdaQueryWrapper<PhysioExam> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(q.getTitle()),PhysioExam::getTitle, q.getTitle());
wrapper.orderByAsc(PhysioExam::getSubject);
return wrapper;
});
Page<PhysioExam> physioExamPage = examMapper.selectPage(page, lambdaQueryWrapper);
List<PhysioExam> records = physioExamPage.getRecords();
if (CollectionUtils.isEmpty(records)){
return new Page<>();
}
List<PhysioExamVO> vos = new ArrayList<>();
for (PhysioExam exam : records) {
PhysioExamVO vo = new PhysioExamVO();
String answer = exam.getAnswer();
List<String> answers = Arrays.asList(answer.split("&"));
vo.setTitle(exam.getTitle());
vo.setAnswers(answers);
vo.setCorrectAnswer(exam.getCorrectAnswer());
vo.setType(exam.getType());
vo.setId(exam.getId());
vo.setSubject(exam.getSubject());
vos.add(vo);
}
Page<PhysioExamVO> voPage = new Page<>();
voPage.setRecords(vos);
voPage.setTotal(physioExamPage.getTotal());
voPage.setCurrent(physioExamPage.getCurrent());
voPage.setSize(physioExamPage.getSize());
return voPage;
}
@PostMapping("/saveOrUpdate")
public Integer saveOrUpdate(@RequestBody PhysioExam exam){
if (exam.getAnswer() == null || !exam.getAnswer().contains("&")){
throw new ServiceException("选项格式不正确");
}
if (exam.getSubject() == null){
throw new ServiceException("subject必填");
}
if (exam.getId() == null){
return examMapper.insert(exam);
}else {
return examMapper.updateById(exam);
}
}
@PostMapping("/deleteExamTitle")
public Integer deleteExamTitle(@RequestParam(value = "id",required = true) Integer id){
return examMapper.deleteById(id);
}
@PostMapping("/addPhysio")
public boolean addPhysio(@RequestBody JkPhysio jkPhysio){
JkPhysio one = physioService.getOne(new LambdaQueryWrapper<JkPhysio>().eq(JkPhysio::getPhysioTel, jkPhysio.getPhysioTel()));
if (Objects.nonNull(one)){
throw new ServiceException("该手机号已被其他理疗师注册");
}
jkPhysio.setAudit(1);
return physioService.save(jkPhysio);
}
@PostMapping("/physioList")
public Page<PhysioVO> page(@RequestBody UniPage<PhysioVO> uniPage){
Page<JkPhysio> page = (Page<JkPhysio>) uniPage.getPage();
LambdaQueryWrapper<JkPhysio> queryWrapper = uniPage.buildQueryWrapper(query -> {
LambdaQueryWrapper<JkPhysio> wrapper = new LambdaQueryWrapper<>();
if (query.getAudit() == null){
wrapper.eq(JkPhysio::getAudit,1);
}else {
wrapper.eq(JkPhysio::getAudit,query.getAudit());
}
boolean hasKeyword = StringUtils.isNotBlank(query.getKeyword());
wrapper.eq(query.getStoreId() != null, JkPhysio::getStoreId, query.getStoreId())
.and(hasKeyword,w -> {
w.eq(hasKeyword, JkPhysio::getId, query.getId())
.or()
.eq(hasKeyword, JkPhysio::getPhysioTel, query.getKeyword())
.or()
.eq(hasKeyword, JkPhysio::getIdcard, query.getKeyword())
.or()
.like(hasKeyword, JkPhysio::getPhysioName, query.getKeyword());
});
return wrapper;
});
Page<JkPhysio> physioPage = physioService.page(page, queryWrapper);
List<JkPhysio> records = physioPage.getRecords();
List<PhysioVO> list = new ArrayList<>();
records.forEach(jkPhysio -> {
PhysioVO vo = new PhysioVO();
BeanUtils.copyProperties(jkPhysio,vo);
JkStore jkStore = storeService.getById(jkPhysio.getStoreId());
vo.setStoreName(jkStore.getStoreName());
list.add(vo);
});
Page<PhysioVO> physioVOPage = new Page<>();
physioVOPage.setRecords(list);
physioVOPage.setCurrent(physioPage.getCurrent());
physioVOPage.setSize(physioPage.getSize());
physioVOPage.setTotal(physioPage.getTotal());
return physioVOPage;
}
@PostMapping("/editPhysioStatus")
public boolean editStatus(@RequestParam("id") Integer id,@RequestParam("status") String status){
JkPhysio jkPhysio = new JkPhysio();
jkPhysio.setId(id);
jkPhysio.setStatus(status);
return physioService.updateById(jkPhysio);
}
@PostMapping("/editPhysio")
public boolean editPhysio(@RequestBody JkPhysio jkPhysio){
JkPhysio one = physioService.getOne(new LambdaQueryWrapper<JkPhysio>().eq(JkPhysio::getPhysioTel, jkPhysio.getPhysioTel()).ne(JkPhysio::getId, jkPhysio.getId()));
if (Objects.nonNull(one)){
throw new ServiceException("该手机号已被其他理疗师注册");
}
JkPhysio physio = physioService.getById(jkPhysio.getId());
if (!jkPhysio.getPhysioTel().equals(physio.getPhysioTel())){
MobileAppUser user = mobileAppUserMapper.selectOne(new LambdaQueryWrapper<MobileAppUser>().eq(MobileAppUser::getPhoneNumber, physio.getPhysioTel()));
if (Objects.nonNull(user) && user.getUserType().equals("P")){
user.setUserType("C");
mobileAppUserMapper.updateById(user);
redisTemplate.delete(CACHE_TOKEN_KEY + physio.getPhysioTel());
redisTemplate.delete(CACHE_TOKEN_PREFIX + physio.getPhysioTel());
}
}
return physioService.updateById(jkPhysio);
}
@GetMapping("/reports")
public List<ReportTemplate> list(){
return reportTemplateMapper.selectList(null);
}
@PostMapping("/addReportTemplate")
public int addReportTemplate(@RequestBody ReportTemplate template){
if (template.getId() == null){
return 0;
}
ReportTemplate reportTemplate = reportTemplateMapper.selectById(template.getId());
if (Objects.nonNull(reportTemplate)){
reportTemplate.setText(template.getText());
reportTemplate.setUpdateTime(LocalDateTime.now());
return reportTemplateMapper.updateById(reportTemplate);
}
template.setCreateTime(LocalDateTime.now());
return reportTemplateMapper.insert(template);
}
@PostMapping("/review")
public Boolean review(@RequestParam("status") Integer status,@RequestBody JkPhysio physio){
Integer id = physio.getId();
JkPhysio jkPhysio = new JkPhysio();
if (status == 1){
jkPhysio.setId(id);
jkPhysio.setAudit(1);
return physioService.updateById(jkPhysio);
}
if (status == 2){
jkPhysio.setId(id);
jkPhysio.setAudit(2);
jkPhysio.setRejectReason(physio.getRejectReason());
return physioService.updateById(jkPhysio);
}
return false;
}
@GetMapping("/treatParameter")
public List<TreatParameter> treatParameters(@RequestParam(value = "name",required = false) String name){
return treatParameterMapper.selectList(new LambdaQueryWrapper<TreatParameter>()
.like(StringUtils.isNotBlank(name),TreatParameter::getSymptomsName,name));
}
@PostMapping("/addOrUpdateTreatParameter")
public Integer addOrUpdateTreatParameter(@RequestBody TreatParameter parameter){
if (parameter.getId() == null){
return 0;
}else {
parameter.setFr(together(parameter.getFr(),true));
parameter.setPl(together(parameter.getPl(),true));
parameter.setPw(together(parameter.getPw(),true));
parameter.setUl(together(parameter.getUl(),true));
parameter.setTl(together(parameter.getTl(),false));
return treatParameterMapper.updateById(parameter);
}
}
private String together(String p,boolean f){
if (StringUtils.isBlank(p)){
throw new ServiceException("参数不能为空");
}
if (f){
if (p.length() > 1){
return p;
}else {
return "0" + p;
}
}else {
if (p.length() > 2){
return p;
}else if (p.length() > 1){
return "0" + p;
}else {
return "00" + p;
}
}
}
@PostMapping("/addDietaryRegulation")
public ResultData getPhysio(@Validated @RequestBody SubHealthDietaryRegulation regulation){
SubHealthDietaryRegulation subHealthDietaryRegulation = subHealthDietaryRegulationMapper.selectById(regulation.getSymptomNumber());
if (Objects.nonNull(subHealthDietaryRegulation)){
throw new ServiceException(String.format("已存在【%s】症状编号",regulation.getSymptomNumber()));
}
return ResultData.success(subHealthDietaryRegulationMapper.insert(regulation));
}
@PostMapping("/editDietaryRegulation")
public ResultData editDietaryRegulation(@Validated @RequestBody SubHealthDietaryRegulation regulation){
return ResultData.success(subHealthDietaryRegulationMapper.updateById(regulation));
}
@PostMapping("/deleteDietary")
public ResultData deleteDietary(@RequestParam("symptomNumber") String symptomNumber){
return ResultData.success(subHealthDietaryRegulationMapper.deleteById(symptomNumber));
}
@PostMapping("/listDietary")
public IPage<SubHealthDietaryRegulation> listDietary(@RequestBody UniPage<SubHealthDietaryRegulation> uniPage){
IPage<SubHealthDietaryRegulation> page = (IPage<SubHealthDietaryRegulation>) uniPage.getPage();
SubHealthDietaryRegulation queryBody = uniPage.getQueryBody();
String number = null,description=number;
if (queryBody != null){
number = queryBody.getSymptomNumber();
description = queryBody.getSymptomDescription();
}
List<SubHealthDietaryRegulation> regulations = subHealthDietaryRegulationMapper.selectSubHealth(page, number, description);
page.setRecords(regulations);
return page;
}
@GetMapping("/readDietaryExcel")
public void readDietaryExcel(){
File file = new File("E:\\document\\unizk2\\112.xlsx");
EasyExcel.read(file,SubHealthDietaryRegulation.class,new SubHealthReadListener(subHealthDietaryRegulationMapper)).sheet().doRead();
}
}
@@ -0,0 +1,30 @@
package cn.unizk.controller;
import cn.unizk.mapper.DailySchedulesMapper;
import cn.unizk.mapper.JkPhysioMapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @Author hao
* @Date 2026/5/19 17:30
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/scheduling")
public class SysPhysioPaiBanController {
@Resource
private DailySchedulesMapper dailySchedulesMapper;
@Resource
private JkPhysioMapper jkPhysioMapper;
@GetMapping("/getScheduling")
public void getScheduling(){}
}
@@ -0,0 +1,257 @@
package cn.unizk.controller;
import cn.unizk.entity.po.JkProduct;
import cn.unizk.entity.po.ProductCarousel;
import cn.unizk.entity.po.SysDict;
import cn.unizk.entity.vo.*;
import cn.unizk.enums.DictType;
import cn.unizk.enums.ProductType;
import cn.unizk.exception.ServiceException;
import cn.unizk.service.ProductCarouselService;
import cn.unizk.service.ProductService;
import cn.unizk.service.SysDictService;
import cn.unizk.validatedGroup.DefaultGroup;
import cn.unizk.validatedGroup.UpdateGroup;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/9/3 11:51
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/product")
public class SysProductController {
@Resource
private SysDictService dictService;
@Resource
private ProductService productService;
@Resource
private ProductCarouselService carouselService;
@PostMapping("/list")
@PreAuthorize("hasRole('ADMIN')")
public Page<JkProduct> list(@RequestBody UniPage<ProductReqVO> uniPage) {
Page<JkProduct> page = new Page<>();
page.setCurrent(uniPage.getCurrentPage());
page.setSize(uniPage.getPageSize());
ProductReqVO queryBody = uniPage.getQueryBody();
if (Objects.isNull(queryBody)) {
return productService.page(page, null);
}
LambdaQueryWrapper<JkProduct> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(queryBody.getProductType()), JkProduct::getProductType, queryBody.getProductType())
.eq(StringUtils.isNotBlank(queryBody.getStatus()), JkProduct::getProductStatus, queryBody.getStatus())
.eq("0".equals(queryBody.getStockType()), JkProduct::getProductStock, 0)
.gt("1".equals(queryBody.getStockType()), JkProduct::getProductStock, 0)
.lt("2".equals(queryBody.getStockType()), JkProduct::getProductStock, 100)
.like(StringUtils.isNotBlank(queryBody.getProductName()), JkProduct::getProductName, queryBody.getProductName());
return productService.page(page, wrapper);
}
@PostMapping("/addProduct")
@PreAuthorize("hasRole('ADMIN')")
public boolean addProduct(@Validated(DefaultGroup.class) @RequestBody JkProductVO productVO) {
SysDict one = dictService.getOne(new LambdaQueryWrapper<SysDict>()
.eq(SysDict::getDictType, DictType.product.name())
.eq(SysDict::getDictValue, productVO.getProductType()));
if (Objects.isNull(one)) {
throw new ServiceException("不存在此商品类型");
}
JkProduct product = new JkProduct();
BeanUtils.copyProperties(productVO,product);
List<ProductCarousel> productCarousels = productVO.getProductCarousels();
if (CollectionUtils.isNotEmpty(productCarousels)){
product.setProductImage(productCarousels.get(0).getImageUrl());
}
productService.save(product);
if (CollectionUtils.isNotEmpty(productCarousels)){
productCarousels.forEach(s -> {
s.setProductId(product.getProductId());
s.setType("inner");
});
carouselService.saveBatch(productCarousels);
}
return true;
}
@PostMapping("/editProduct")
@PreAuthorize("hasRole('ADMIN')")
public boolean editPorduct(@Validated(UpdateGroup.class) @RequestBody JkProductVO vo) {
SysDict one = dictService.getOne(new LambdaQueryWrapper<SysDict>()
.eq(SysDict::getDictType, DictType.product.name())
.eq(SysDict::getDictValue, vo.getProductType()));
if (Objects.isNull(one)) {
throw new ServiceException("不存在此商品类型");
}
JkProduct product = new JkProduct();
BeanUtils.copyProperties(vo,product);
List<ProductCarousel> productCarousels = vo.getProductCarousels();
if (CollectionUtils.isNotEmpty(productCarousels)){
product.setProductImage(productCarousels.get(0).getImageUrl());
}
productService.updateById(product);
if (CollectionUtils.isNotEmpty(productCarousels)){
carouselService.remove(new LambdaQueryWrapper<ProductCarousel>()
.eq(ProductCarousel::getProductId,product.getProductId()));
productCarousels.forEach(s -> {
s.setProductId(product.getProductId());
s.setType("inner");
});
carouselService.saveBatch(productCarousels);
}
return true;
}
@PostMapping("/echoEditProduct")
@PreAuthorize("hasRole('ADMIN')")
public JkProductVO echoEditProduct(@RequestParam("id") Integer id){
return productService.echo(id);
}
@GetMapping("/listType")
@PreAuthorize("hasRole('ADMIN')")
public Page<ProductTypeVO> listType(UniPage uniPage, @RequestParam(value = "status", required = false) String status,
@RequestParam(value = "typeName", required = false) String typeName) {
Page<SysDict> page = new Page<>();
page.setSize(uniPage.getPageSize());
page.setCurrent(uniPage.getCurrentPage());
Page<SysDict> dictPage = dictService.page(page, new LambdaQueryWrapper<SysDict>()
.eq(SysDict::getDictType, DictType.product.name())
.eq(StringUtils.isNotBlank(status), SysDict::getStatus, status)
.like(StringUtils.isNotBlank(typeName), SysDict::getDictValue, typeName));
List<SysDict> list = dictPage.getRecords();
List<ProductTypeVO> productTypeVOS = new ArrayList<>();
list.forEach(sysDict -> {
ProductTypeVO vo = new ProductTypeVO();
vo.setDictCode(sysDict.getDictCode());
vo.setPcDisplayImage(sysDict.getPcDisplayImage());
vo.setStatus(sysDict.getStatus());
vo.setTypeName(sysDict.getDictValue());
productTypeVOS.add(vo);
});
Page<ProductTypeVO> productTypeVOPage = new Page<>();
productTypeVOPage.setCurrent(dictPage.getCurrent());
productTypeVOPage.setSize(dictPage.getSize());
productTypeVOPage.setRecords(productTypeVOS);
productTypeVOPage.setTotal(dictPage.getTotal());
return productTypeVOPage;
}
@PostMapping("/addType")
@PreAuthorize("hasRole('ADMIN')")
public ResultData addType(@RequestBody ProductTypeVO vo) {
if (StringUtils.isBlank(vo.getTypeName()) || StringUtils.isBlank(vo.getStatus())) {
return ResultData.success();
}
SysDict one = dictService.getOne(new LambdaQueryWrapper<SysDict>()
.eq(SysDict::getDictType, DictType.product.name())
.eq(SysDict::getDictValue, vo.getTypeName()));
if (Objects.nonNull(one)) {
return ResultData.failure(500, "已存在该商品类型");
}
SysDict sysDict = new SysDict();
sysDict.setDictType(DictType.product.name());
sysDict.setDictValue(vo.getTypeName());
sysDict.setPcDisplayImage(vo.getPcDisplayImage());
sysDict.setStatus(vo.getStatus());
dictService.save(sysDict);
return ResultData.success();
}
@PostMapping("/editProductType")
@PreAuthorize("hasRole('ADMIN')")
public ResultData editProductType(@Valid @RequestBody ProductTypeVO vo) {
SysDict one = dictService.getOne(new LambdaQueryWrapper<SysDict>()
.eq(SysDict::getDictType, DictType.product.name())
.eq(SysDict::getDictValue, vo.getTypeName())
.ne(SysDict::getDictCode, vo.getDictCode()));
if (Objects.nonNull(one)) {
throw new ServiceException("已存在该商品类型");
}
SysDict sysDict = new SysDict();
sysDict.setDictValue(vo.getTypeName());
sysDict.setDictCode(vo.getDictCode());
sysDict.setPcDisplayImage(vo.getPcDisplayImage());
sysDict.setStatus(vo.getStatus());
sysDict.setDictType(DictType.product.name());
dictService.updateById(sysDict);
return ResultData.success();
}
@PostMapping("/deleteProductType/{dictCode}")
@PreAuthorize("hasRole('ADMIN')")
public boolean deleteType(@PathVariable("dictCode") Integer dictCode){
SysDict sysDict = dictService.getById(dictCode);
List<JkProduct> products = productService.list(new LambdaQueryWrapper<JkProduct>().eq(JkProduct::getProductType, sysDict.getDictValue()));
if (CollectionUtils.isNotEmpty(products)){
throw new ServiceException("存在此类型的商品,请先删除商品");
}
return dictService.remove(new LambdaQueryWrapper<SysDict>()
.eq(SysDict::getDictType,DictType.product.name()).eq(SysDict::getDictCode,dictCode));
}
@PostMapping("/deleteProducts")
@PreAuthorize("hasRole('ADMIN')")
public boolean deleteProducts(@RequestBody List<Integer> productIds){
if (CollectionUtils.isEmpty(productIds)){
return false;
}
return productService.removeBatchByIds(productIds);
}
@PostMapping("/setUpCarousel")
public boolean setUpCarousel(@RequestParam("productId") Integer productId,@RequestParam("isCarousel") Integer isCarousel){
if (isCarousel == 1){
List<JkProduct> products = productService.list(new LambdaQueryWrapper<JkProduct>().eq(JkProduct::getProductStatus, "上架")
.eq(JkProduct::getWhetherCarousel, 1));
if (products != null && products.size() >= 3){
throw new ServiceException("首页轮播图最多设置3张,请先移除一张再设置");
}
JkProduct jkProduct = productService.getById(productId);
if (Objects.isNull(jkProduct)){
throw new ServiceException("此商品不存在");
}
jkProduct.setWhetherCarousel(1);
productService.updateById(jkProduct);
// carouselService.remove(new LambdaQueryWrapper<ProductCarousel>().eq(ProductCarousel::getType, "outer"));
ProductCarousel carousel = new ProductCarousel();
carousel.setProductId(productId);
carousel.setImageUrl(jkProduct.getProductImage());
carousel.setType("outer");
return carouselService.save(carousel);
}else if (isCarousel == 0){
List<JkProduct> products = productService.list(new LambdaQueryWrapper<JkProduct>().eq(JkProduct::getProductStatus, "上架")
.eq(JkProduct::getWhetherCarousel, 1));
if (products == null || products.size() < 2){
throw new ServiceException("至少保留一张轮播图");
}
JkProduct jkProduct = productService.getById(productId);
jkProduct.setWhetherCarousel(0);
productService.updateById(jkProduct);
return carouselService.remove(new LambdaQueryWrapper<ProductCarousel>()
.eq(ProductCarousel::getProductId,productId)
.eq(ProductCarousel::getType,"outer"));
}
return false;
}
}
@@ -0,0 +1,184 @@
package cn.unizk.controller;
import cn.unizk.entity.po.BusinessChannel;
import cn.unizk.entity.po.Device;
import cn.unizk.entity.po.JkStore;
import cn.unizk.entity.po.SysUser;
import cn.unizk.entity.vo.JkStoreReqVO;
import cn.unizk.entity.vo.JkStoreResVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.DeviceMapper;
import cn.unizk.service.BusinessChannelService;
import cn.unizk.service.StoreService;
import cn.unizk.service.SysUserService;
import cn.unizk.utils.BeanCopyUtils;
import cn.unizk.utils.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/9/8 11:58
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin")
public class SysStoreController {
@Resource
private StoreService storeService;
@Resource
private SysUserService userService;
@Resource
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Resource
private BusinessChannelService businessChannelService;
@Resource
private DeviceMapper deviceMapper;
@PostMapping("/storeList")
public Page<JkStoreResVO> list(@RequestBody UniPage<JkStoreReqVO> uniPage){
Page<JkStore> page = (Page<JkStore>) uniPage.getPage();
LambdaQueryWrapper<JkStore> lambdaQueryWrapper = uniPage.buildQueryWrapper(query -> {
LambdaQueryWrapper<JkStore> w = new LambdaQueryWrapper<>();
String keyword = query.getKeyword();
boolean hasKeyword = StringUtils.isNotBlank(keyword);
w.eq(StringUtils.isNotBlank(query.getStatus()),JkStore::getStatus,query.getStatus());
w.and(hasKeyword, wrapper ->
wrapper.like(hasKeyword, JkStore::getStoreName, keyword)
.or()
.like(hasKeyword, JkStore::getAddress, keyword)
.or()
.eq(hasKeyword, JkStore::getContactTel, keyword)
);
return w;
});
Page<JkStore> jkStorePage = storeService.page(page, lambdaQueryWrapper);
Page<JkStoreResVO> resVOPage = new Page<>();
resVOPage.setCurrent(jkStorePage.getCurrent());
resVOPage.setSize(jkStorePage.getSize());
resVOPage.setTotal(jkStorePage.getTotal());
List<JkStore> records = jkStorePage.getRecords();
Collection<JkStoreResVO> jkStoreResVOS = BeanCopyUtils.copyArrays(records, JkStoreResVO::new);
for (JkStoreResVO vo : jkStoreResVOS) {
Integer channelId = vo.getChannelId();
if (channelId == null){
continue;
}
BusinessChannel businessChannel = businessChannelService.getById(channelId);
vo.setBusinessChannel(businessChannel);
}
resVOPage.setRecords((List<JkStoreResVO>) jkStoreResVOS);
return resVOPage;
}
@GetMapping("/echoAccount")
public JkStoreResVO echoAccount(@RequestParam("storeId") Integer storeId){
SysUser one = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getStoreId, storeId));
JkStore byId = storeService.getById(storeId);
JkStoreResVO vo = new JkStoreResVO();
vo.setContactTel(byId.getContactTel());
if (Objects.nonNull(one)){
vo.setSysUser(one);
}
vo.setId(storeId);
return vo;
}
@PostMapping("/initAccount")
public ResultData initAccount(@RequestBody JkStoreReqVO vo){
if (vo.getId() == null){
throw new ServiceException("门店id必传");
}
if (StringUtils.isBlank(vo.getUsername()) || StringUtils.isBlank(vo.getPassword())){
throw new ServiceException("账号密码必传");
}
JkStore jkStore = storeService.getById(vo.getId());
if (Objects.isNull(jkStore)){
throw new ServiceException("无此门店,请核对信息");
}
if (StringUtils.isNotBlank(vo.getContactTel())){
jkStore.setContactTel(vo.getContactTel());
}
SysUser one = userService.getOne(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getStoreId, vo.getId()));
storeService.updateById(jkStore);
String encode = bCryptPasswordEncoder.encode(vo.getPassword());
if (Objects.isNull(one)){
SysUser user = new SysUser();
user.setPassword(encode);
user.setCreateTime(LocalDateTime.now());
user.setUsername(vo.getUsername());
userService.save(user);
}else {
one.setUsername(vo.getUsername());
one.setPassword(encode);
one.setStoreId(vo.getId());
one.setUpdateTime(LocalDateTime.now());
userService.updateById(one);
}
return ResultData.success();
}
@PostMapping("/closeOrOpen")
public ResultData closed(@RequestParam("storeId") Integer storeId,@RequestParam("status") String status){
JkStore jkStore = storeService.getById(storeId);
if (Objects.isNull(jkStore)){
return ResultData.success();
}
jkStore.setStatus(status);
storeService.updateById(jkStore);
return ResultData.success();
}
@PostMapping("/addStore")
public ResultData addStore(@RequestBody JkStoreReqVO vo){
JkStore loLa = MapUtil.getLoLa(vo.getAddress());
JkStore jkStore = new JkStore();
BeanUtils.copyProperties(vo,jkStore);
if (loLa != null){
jkStore.setLatitude(loLa.getLatitude());
jkStore.setLongitude(loLa.getLongitude());
}
storeService.save(jkStore);
SysUser user = new SysUser();
user.setStoreId(jkStore.getId());
user.setUsername(vo.getUsername());
user.setPassword(bCryptPasswordEncoder.encode(vo.getPassword()));
user.setCreateTime(LocalDateTime.now());
userService.save(user);
return ResultData.success();
}
@PostMapping("/editStore")
public boolean editStore(@RequestBody JkStore jkStore){
List<Device> deviceList = deviceMapper.selectList(new LambdaQueryWrapper<Device>().eq(Device::getStoreId, jkStore.getId()));
if (CollectionUtils.isNotEmpty(deviceList)){
deviceList.forEach(device -> {
device.setAffiliatedWith(jkStore.getStoreName());
});
deviceMapper.updateById(deviceList);
}
JkStore loLa = MapUtil.getLoLa(jkStore.getAddress());
if (loLa != null){
jkStore.setLongitude(loLa.getLongitude());
jkStore.setLatitude(loLa.getLatitude());
}
return storeService.updateById(jkStore);
}
}
@@ -0,0 +1,114 @@
package cn.unizk.controller;
import cn.unizk.entity.po.SysUser;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.SysUserVO2;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.service.SysUserService;
import cn.unizk.utils.SecurityUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* @Author hao
* @Date 2026/6/6 17:30
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/admin/sysUser")
public class SysUserController {
@Resource
private SysUserService sysUserService;
@Resource
private BCryptPasswordEncoder bCryptPasswordEncoder;
@PostMapping("/getSysUsers")
public Page<SysUser> getSysUser(@RequestBody UniPage<SysUser> uniPage){
Page<SysUser> page = (Page<SysUser>) uniPage.getPage();
LambdaQueryWrapper<SysUser> queryWrapper = uniPage.buildQueryWrapper(wrapper -> {
LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(StringUtils.isNotBlank(wrapper.getUsername()), SysUser::getUsername, wrapper.getUsername())
.eq(StringUtils.isNotBlank(wrapper.getStatus()), SysUser::getStatus, wrapper.getStatus())
.eq(SysUser::getRole,"ROLE_ADMIN");
return lambdaQueryWrapper;
});
return sysUserService.page(page,queryWrapper);
}
@PostMapping("/editPwd")
public ResultData addOrUpdate(@RequestBody SysUserVO2 sysUserVO2){
SysUser currentUser = SecurityUtils.getSysUser();
SysUser user = sysUserService.getById(currentUser.getId());
if (!currentUser.getUsername().equals("admin") && !bCryptPasswordEncoder.matches(sysUserVO2.getOriginalPassword(),user.getPassword())){
throw new ServiceException("您输入的原始密码不正确!");
}
/*if (!"admin".equals(currentUser.getUsername())){
if (!user.getId().equals(sysUserVO2.getId())){
throw new ServiceException("您无权修改他人的账户密码");
}
}*/
SysUser byId = sysUserService.getById(sysUserVO2.getId());
if (Objects.isNull(byId)) throw new ServiceException("用户不存在");
if (byId.getUsername().equals("admin")){
throw new ServiceException("无权修改admin账户");
}
SysUser sysUser = new SysUser();
sysUser.setId(sysUserVO2.getId());
sysUser.setPassword(bCryptPasswordEncoder.encode(sysUserVO2.getNewPassword()));
sysUser.setRole("ROLE_ADMIN");
sysUser.setUpdateTime(LocalDateTime.now());
sysUserService.updateById(sysUser);
return ResultData.success();
}
@PostMapping("/editSysUser")
public ResultData editSysUser(@RequestBody SysUser sysUser){
SysUser user = SecurityUtils.getSysUser();
/*if (!user.getUsername().equals("admin") && !user.getId().equals(sysUser.getId())){
throw new ServiceException("您无权修改他人的账户密码!");
}*/
SysUser byId = sysUserService.getById(sysUser.getId());
if (Objects.isNull(byId)) throw new ServiceException("用户不存在");
if (byId.getUsername().equals("admin") && !user.getUsername().equals("admin")) throw new ServiceException("无权修改admin账户");
sysUser.setPassword(null);
sysUser.setUpdateTime(LocalDateTime.now());
sysUserService.updateById(sysUser);
return ResultData.success();
}
@PostMapping("/addSysUser")
public ResultData addSysUser(@RequestBody SysUser sysUser){
if ("admin".equals(sysUser.getDept())){
sysUser.setDept(null);
}
SysUser one = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, sysUser.getUsername()));
if (Objects.nonNull(one)){
throw new ServiceException(String.format("【%s】用户名已存在",one.getUsername()));
}
sysUser.setId(null);
String encode = bCryptPasswordEncoder.encode(sysUser.getPassword());
sysUser.setPassword(encode);
sysUser.setRole("ROLE_ADMIN");
sysUser.setCreateTime(LocalDateTime.now());
return sysUserService.save(sysUser) ? ResultData.success() : ResultData.failure();
}
@GetMapping("/deleteSysUser")
public ResultData delete(@RequestParam("id") Integer id){
SysUser sysUser = SecurityUtils.getSysUser();
if (!sysUser.getId().equals(id) && !sysUser.getUsername().equals("admin")) throw new ServiceException("您无权删除他人账号!");
sysUserService.removeById(id);
return ResultData.success();
}
}
@@ -0,0 +1,34 @@
package cn.unizk.controller;
import cn.unizk.entity.po.UserTreatmentParameters;
import cn.unizk.mapper.UserTreatmentParametersMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @Author hao
* @Date 2026/5/8 11:50
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/userTreat")
public class UserTreatController {
@Resource
private UserTreatmentParametersMapper userTreatmentParametersMapper;
@GetMapping("/lookTreat")
public List<UserTreatmentParameters> lookTreat(@RequestParam(value = "deviceId",required = false) String deviceId){
if (StringUtils.isBlank(deviceId)){
return new ArrayList<>();
}
return userTreatmentParametersMapper.selectList(new LambdaQueryWrapper<UserTreatmentParameters>()
.eq(UserTreatmentParameters::getStatus,1).eq(UserTreatmentParameters::getDeviceId,deviceId));
}
}
@@ -0,0 +1,68 @@
package cn.unizk.controller;
import cn.unizk.exception.ServiceException;
import cn.unizk.properties.SysWxProperties;
import cn.unizk.service.SysOrderService;
import cn.unizk.service.WxAuthLoginService;
import cn.unizk.utils.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/9/11 10:39
* @Version 1.0
**/
@RestController
@RequestMapping("/wx")
public class WxAuthLoginController {
@Resource
private WxAuthLoginService wxAuthLoginService;
@Resource
private SysWxProperties wxProperties;
/*@Resource
private WxUserService userService;*/
@Resource
private SysOrderService orderService;
private static String getTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=${1}&secret=${2}&code=${3}&grant_type=authorization_code";
@GetMapping("/auth")
public void auth(HttpServletResponse response,@RequestParam("deviceId") String deviceId) throws IOException {
wxAuthLoginService.auth(response,deviceId);
}
/* @GetMapping("/authCallback")
public void authCallback(HttpServletResponse response,@RequestParam("code") String code,@RequestParam("state") String state) throws IOException {
getTokenUrl = getTokenUrl.replace("${1}",wxProperties.getAppid()).replace("${2}",wxProperties.getSecret())
.replace("${3}",code);
String s = HttpUtil.doGet(getTokenUrl, null, null);
JSONObject res = JSONObject.parseObject(s);
String openid = res.getString("openid");
WxUser wxUser = userService.getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getOpenid, openid));
if (Objects.isNull(wxUser)){
throw new ServiceException("您不是吉客中医微信小程序的用户");
}
List<WxOrder> wxOrders = orderService.getBaseMapper().selectList(new LambdaQueryWrapper<WxOrder>().eq(WxOrder::getOpenid, openid)
.eq(WxOrder::getDeviceId, state));
if (CollectionUtils.isEmpty(wxOrders)){
throw new ServiceException("抱歉,您在此设备上没有购买套餐");
}
//重定向到h5页面,携带openid和deviceId
String redirectUrl = "http://369.peaktcm.com:9000/combo.html?deviceId=" + state + "&openid=" + openid;
response.sendRedirect(redirectUrl);
}*/
}
@@ -0,0 +1,111 @@
package cn.unizk.controller;
import cn.unizk.entity.po.JkPhysio;
import cn.unizk.entity.po.JkStore;
import cn.unizk.entity.po.MobileAppUser;
import cn.unizk.entity.po.UserFreeTrialInfo;
import cn.unizk.entity.vo.AppUserDetailsVO;
import cn.unizk.entity.vo.MobileAppUserVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.JkPhysioMapper;
import cn.unizk.mapper.MobileAppUserMapper;
import cn.unizk.mapper.StoreMapper;
import cn.unizk.mapper.UserFreeTrialInfoMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/9/8 11:25
* @Version 1.0
**/
@RestController
@RequestMapping("/pc/wxUser")
public class WxUserController {
/* @Resource
private WxUserService wxUserService;
*/
@Resource
private MobileAppUserMapper mobileAppUserMapper;
@Resource
private UserFreeTrialInfoMapper userFreeTrialInfoMapper;
@Resource
private JkPhysioMapper jkPhysioMapper;
@Resource
private StoreMapper storeMapper;
@PostMapping("/list")
@PreAuthorize("hasRole('ADMIN')")
public Page<MobileAppUser> list(@RequestBody UniPage<MobileAppUserVO> uniPage){
Page<MobileAppUser> page = (Page<MobileAppUser>) uniPage.getPage();
LambdaQueryWrapper<MobileAppUser> queryWrapper = uniPage.buildQueryWrapper(query -> {
boolean hasKeyword = StringUtils.isNotBlank(query.getKeyword());
LambdaQueryWrapper<MobileAppUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(query.getId() != null, MobileAppUser::getId, query.getId())
.and(hasKeyword,w -> {
w.eq(hasKeyword, MobileAppUser::getPhoneNumber, query.getKeyword())
.or()
.like(hasKeyword, MobileAppUser::getNickname, query.getKeyword());
});
return wrapper;
});
return mobileAppUserMapper.selectPage(page,queryWrapper);
}
@GetMapping("/getUserDetails")
public AppUserDetailsVO getUserDetails(@RequestParam("appUserId") Integer appUserId){
UserFreeTrialInfo userFreeTrialInfo = userFreeTrialInfoMapper.selectOne(new LambdaQueryWrapper<UserFreeTrialInfo>()
.eq(UserFreeTrialInfo::getUserId, appUserId));
MobileAppUser user = mobileAppUserMapper.selectById(appUserId);
AppUserDetailsVO vo = new AppUserDetailsVO();
if (user.getFirstPhysioId() != null && user.getFirstStoreId() != null){
JkPhysio jkPhysio = jkPhysioMapper.selectById(user.getFirstPhysioId());
JkStore jkStore = storeMapper.selectById(user.getFirstStoreId());
vo.setFirstPhysioName(jkPhysio.getPhysioName());
vo.setFirstStoreName(jkStore.getStoreName());
}
if (Objects.isNull(userFreeTrialInfo)){
return vo;
}
vo.setSymptomPosition(userFreeTrialInfo.getSymptomPosition());
Map<Integer,String> castLevelMap = new HashMap<>();
castLevelMap.put(1,"轻度");
castLevelMap.put(2,"中度");
castLevelMap.put(3,"重度");
vo.setStrainLevel(castLevelMap.get(userFreeTrialInfo.getStrainLevel()));
vo.setRestrict(castLevelMap.get(userFreeTrialInfo.getRestrictedDegree()));
castLevelMap.clear();
castLevelMap.put(1,"低");
castLevelMap.put(2,"中");
castLevelMap.put(3,"高");
vo.setConsumptionLevel(castLevelMap.get(userFreeTrialInfo.getConsumptionLevel()));
return vo;
}
@PostMapping("/writeOff")
public ResultData writeOff(@RequestParam("appUserId") Integer appUserId){
UserFreeTrialInfo userFreeTrialInfo = userFreeTrialInfoMapper.selectOne(new LambdaQueryWrapper<UserFreeTrialInfo>()
.eq(UserFreeTrialInfo::getUserId, appUserId));
if (Objects.isNull(userFreeTrialInfo)){
throw new ServiceException("用户尚未填写首次免费体验信息,无法核销!");
}
MobileAppUser user = new MobileAppUser();
user.setId(appUserId);
user.setFreeTrial(0);
mobileAppUserMapper.updateById(user);
return ResultData.success();
}
}
@@ -0,0 +1,26 @@
package cn.unizk.entity.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
* @Author hao
* @Date 2026/9/17 11:55
* @Version 1.0
**/
@Data
public class AppointmentDTO {
private String physioName;
private String patientName;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd",timezone="GMT+8")
private LocalDate visitDate;
private String status;
}
@@ -0,0 +1,91 @@
package cn.unizk.entity.dto;
import cn.unizk.entity.po.JkStore;
import cn.unizk.entity.po.SysUser;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Author hao
* @Date 2025/8/22 18:37
* @Version 1.0
**/
@Getter
@Setter
public class LoginUser implements UserDetails {
private Integer id;
private String token;
private SysUser sysUser;
private Long loginTime;
private Long expireTime;
/**
* 权限列表
*/
private Set<String> permissions;
public LoginUser(){}
public LoginUser(Integer userId,SysUser sysUser,Set<String> permissions){
this.id = userId;
this.sysUser = sysUser;
this.permissions = permissions;
}
public LoginUser(SysUser sysUser){
this.sysUser = sysUser;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (this.permissions == null) {
return Collections.emptyList();
}
return permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return sysUser.getPassword();
}
@Override
public String getUsername() {
return sysUser.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@@ -0,0 +1,19 @@
package cn.unizk.entity.vo;
import com.alibaba.fastjson2.JSONObject;
/**
* @Author hao
* @Date 2025/10/20 18:31
* @Version 1.0
**/
public class InStockVO {
private Integer rescode;
private Boolean success = true;
private String resmsg = "success_";
private JSONObject data;
}
@@ -0,0 +1,18 @@
package cn.unizk.entity.vo;
import lombok.Data;
/**
* @Author hao
* @Date 2026/5/19 17:33
* @Version 1.0
**/
@Data
public class PhysioScheduleVO {
private String m1;
private String m2;
}
@@ -0,0 +1,23 @@
package cn.unizk.entity.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @Author hao
* @Date 2025/9/3 11:56
* @Version 1.0
**/
@Data
public class ProductTypeVO {
@NotNull(message = "dictCode cannot be null")
private Integer dictCode;
@NotBlank(message = "typeName cannot be null")
private String typeName;
private String pcDisplayImage;
@NotBlank(message = "status cannot be null")
private String status;
}
@@ -0,0 +1,21 @@
package cn.unizk.entity.vo;
import cn.unizk.entity.po.SysUser;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author hao
* @Date 2025/8/25 15:04
* @Version 1.0
**/
@Data
@EqualsAndHashCode(callSuper = true)
public class SysUserVO extends SysUser {
private String token;
private String storeName;
private String storeImage;
}
@@ -0,0 +1,7 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.JkPhysio;
public interface JkPhysioMapper extends UniBaseMapper<JkPhysio> {
}
@@ -0,0 +1,10 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.JkProduct;
import cn.unizk.entity.vo.JkProductVO;
import org.apache.ibatis.annotations.Param;
public interface JkProductMapper extends UniBaseMapper<JkProduct> {
JkProductVO selectProductVO(@Param("id") Integer id);
}
@@ -0,0 +1,12 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.Menu;
import java.util.List;
public interface MenuMapper extends UniBaseMapper<Menu> {
List<Menu> selectAdminMenuList();
List<Menu> selectStoreMenuList();
}
@@ -0,0 +1,6 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.ProductCarousel;
public interface ProductCarouselMapper extends UniBaseMapper<ProductCarousel> {
}
@@ -0,0 +1,6 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.JkStore;
public interface StoreMapper extends UniBaseMapper<JkStore> {
}
@@ -0,0 +1,6 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.SysDict;
public interface SysDictMapper extends UniBaseMapper<SysDict> {
}
@@ -0,0 +1,6 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.SysFile;
public interface SysFileMapper extends UniBaseMapper<SysFile> {
}
@@ -0,0 +1,6 @@
package cn.unizk.mapper;
import cn.unizk.entity.po.SysUser;
public interface SysUserMapper extends UniBaseMapper<SysUser> {
}
@@ -0,0 +1,6 @@
package cn.unizk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UniBaseMapper<T> extends BaseMapper<T> {
}
@@ -0,0 +1,18 @@
/*
package cn.unizk.mapper;
import cn.unizk.entity.po.WxOrder;
import cn.unizk.entity.vo.OrderReqVO;
import cn.unizk.entity.vo.WxOrderVO;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
public interface WxOrderMapper extends UniBaseMapper<WxOrder> {
Page<WxOrderVO> selectOrders(Page<WxOrderVO> page, @Param("vo") OrderReqVO vo);
WxOrder selectOrderByIdAndPhone(@Param("orderId") String orderId,@Param("phone") String phone);
Page<WxOrderVO> selectAfterSaleOrders(Page<WxOrderVO> page, @Param("vo") OrderReqVO vo);
}
*/
@@ -0,0 +1,15 @@
/*
package cn.unizk.mapper;
import cn.unizk.entity.po.WxUser;
*/
/**
* @Author hao
* @Date 2025/9/8 11:27
* @Version 1.0
**//*
public interface WxUserMapper extends UniBaseMapper<WxUser> {
}
*/
@@ -0,0 +1,70 @@
package cn.unizk.properties;
import cn.unizk.annotation.Anonymous;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
import java.util.regex.Pattern;
/**
* @Author hao
* @Date 2025/8/22 19:21
* @Version 1.0
**/
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)}");
private ApplicationContext applicationContext;
private List<String> urls = new ArrayList<>();
public String ASTERISK = "*";
@Override
public void afterPropertiesSet()
{
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable 为 *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
// 获取类上边的注解, 替代path variable 为 *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
});
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException
{
this.applicationContext = context;
}
public List<String> getUrls()
{
return urls;
}
public void setUrls(List<String> urls)
{
this.urls = urls;
}
}
@@ -0,0 +1,24 @@
package cn.unizk.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author hao
* @Date 2025/9/11 10:12
* @Version 1.0
**/
@Component
@ConfigurationProperties(prefix = "wx")
@Getter
@Setter
public class SysWxProperties {
private String appid;
private String secret;
private String redirectUrl;
}
@@ -0,0 +1,25 @@
package cn.unizk.security.context;
import org.springframework.security.core.Authentication;
/**
* @Author hao
* @Date 2025/8/25 14:49
* @Version 1.0
**/
public class AuthenticationContextHolder {
private static final ThreadLocal<Authentication> AUTHENTICATION_THREAD_LOCAL = new ThreadLocal<>();
public static Authentication getContext(){
return AUTHENTICATION_THREAD_LOCAL.get();
}
public static void setContext(Authentication authentication){
AUTHENTICATION_THREAD_LOCAL.set(authentication);
}
public static void removeContext(){
AUTHENTICATION_THREAD_LOCAL.remove();
}
}
@@ -0,0 +1,46 @@
package cn.unizk.security.filter;
import cn.unizk.entity.dto.LoginUser;
import cn.unizk.service.TokenService;
import cn.unizk.utils.SecurityUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/8/22 18:54
* @Version 1.0
**/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain chain)
throws ServletException, IOException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (Objects.nonNull(loginUser) && Objects.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
}
@@ -0,0 +1,57 @@
package cn.unizk.security.handler;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.utils.ServletUtils;
import com.alibaba.fastjson2.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
/**
* @Author hao
* @Date 2025/8/22 18:23
* @Version 1.0
**/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Autowired
private RequestMappingHandlerMapping handlerMapping;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException {
/*
该写法能让用户未登录的情况下访问不存在的路径返回404而非401,但springsecurity会让所有非放行路径名单里的路径(包括
不存在的路径)都需要登录认证。这是合乎逻辑的,所以我认为没有必要这样写.
而且,如果是登录后并且有权限的情况下,访问不存在的路径,是正常返回404的。
*/
/*HandlerExecutionChain chain = null;
ServletRequestPathUtils.parseAndCache(request);
try {
chain = handlerMapping.getHandler(request);
} catch (Exception e1) {
e1.printStackTrace();
}
if (chain == null){
String msg = String.format("请求访问:%s,资源路径不存在", request.getRequestURI());
ServletUtils.renderString(response, JSON.toJSONString(ResultData.failure(404, msg)));
}else {
String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI());
ServletUtils.renderString(response, JSON.toJSONString(ResultData.failure(401, msg)));
}*/
String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI());
ServletUtils.renderString(response, JSON.toJSONString(ResultData.failure(401, msg)));
}
}
@@ -0,0 +1,50 @@
package cn.unizk.security.handler;
import cn.unizk.entity.dto.LoginUser;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.service.TokenService;
import cn.unizk.utils.ServletUtils;
import com.alibaba.fastjson2.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
/**
* @Author hao
* @Date 2025/8/22 18:27
* @Version 1.0
**/
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
@Autowired
private TokenService tokenService;
/**
* 退出处理
*
* @return
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (Objects.nonNull(loginUser))
{
String userName = loginUser.getUsername();
// 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken());
// 记录用户退出日志
// AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
}
ServletUtils.renderString(response, JSON.toJSONString(ResultData.success("user.logout.success")));
}
}
@@ -0,0 +1,22 @@
package cn.unizk.security.handler;
import cn.unizk.entity.vo.ResultData;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @Author hao
* @Date 2025/8/26 17:54
* @Version 1.0
**/
/*@RestControllerAdvice
public class SecurityExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
public ResultData handlerAccessDeniedException(AccessDeniedException e){
e.printStackTrace();
return ResultData.failure(403,"无权限访问该资源");
}
}*/
@@ -0,0 +1,29 @@
package cn.unizk.security.handler;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.utils.ServletUtils;
import com.alibaba.fastjson2.JSON;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
/**
* @Author hao
* @Date 2025/8/26 17:02
* @Version 1.0
**/
@Component
public class UserAccessDeniedHandler implements AccessDeniedHandler, Serializable {
private static final long serialVersionUID = 1922706320435419878L;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ServletUtils.renderString(response, JSON.toJSONString(ResultData.failure(403,"权限不足,无法访问该资源")));
}
}
@@ -0,0 +1,8 @@
package cn.unizk.service;
import cn.unizk.entity.po.BusinessChannel;
import com.baomidou.mybatisplus.extension.service.IService;
public interface BusinessChannelService extends IService<BusinessChannel> {
}
@@ -0,0 +1,138 @@
package cn.unizk.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.unizk.config.HuiFuVendorProperties;
import cn.unizk.entity.dto.CashOutDTO;
import cn.unizk.entity.dto.CashOutResponse;
import cn.unizk.entity.po.CashOutRecord;
import cn.unizk.entity.po.JkStore;
import cn.unizk.entity.po.StoreAccount;
import cn.unizk.entity.po.SysUser;
import cn.unizk.exception.ServiceException;
import cn.unizk.mapper.CashOutRecordMapper;
import cn.unizk.mapper.StoreAccountMapper;
import cn.unizk.mapper.StoreMapper;
import cn.unizk.utils.HttpUtil;
import cn.unizk.utils.HuifuSignUtil;
import cn.unizk.utils.SecurityUtils;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;
/**
* @Author hao
* @Date 2026/3/13 15:31
* @Version 1.0
**/
@Service
@Slf4j
public class ChildStoreService {
@Resource
private HuiFuVendorProperties vendorProperties;
@Resource
private StoreMapper jkStoreMapper;
@Resource
private CashOutRecordMapper cashOutRecordMapper;
@Resource
private StoreAccountMapper storeAccountMapper;
private static final String YUE_PAY_API = "https://api.huifu.com/v2/trade/acctpayment/pay";
/**
* 门店提现接口
*
* @param dto
* @return
*/
@Transactional(rollbackFor = Exception.class)
public CashOutResponse profitSharingPay(CashOutDTO dto) {
SysUser sysUser = SecurityUtils.getSysUser();
if (sysUser.getStoreId() == null || !sysUser.getStoreId().equals(dto.getStoreId())){
throw new ServiceException(403,"非法访问");
}
JSONObject data = JSONObject.of();
String snowId = IdUtil.getSnowflakeNextIdStr();
data.put("req_seq_id", snowId);
String now = DateUtil.format(new Date(), "yyyyMMdd");
data.put("req_date", now);
data.put("out_huifu_id", vendorProperties.getHuifuId());
BigDecimal bd = new BigDecimal(dto.getAmount());
bd = bd.setScale(2, RoundingMode.DOWN);
data.put("ord_amt", bd.toString());
JkStore jkStore = jkStoreMapper.selectById(dto.getStoreId());
if (Objects.isNull(jkStore)) {
return new CashOutResponse(500,String.format("门店ID【%d】不存在",dto.getStoreId()));
}
StoreAccount storeAccount = storeAccountMapper.selectOne(new LambdaQueryWrapper<StoreAccount>().eq(StoreAccount::getStoreId, dto.getStoreId()));
if (Objects.isNull(storeAccount) || storeAccount.getAvailableBalance().compareTo(bd) < 0){
throw new ServiceException("提现金额不能大于账户可用余额!");
}
JSONObject acct_split_bunch = JSONObject.of();
JSONArray acct_infos = new JSONArray();
JSONObject accInfo = new JSONObject();
accInfo.put("div_amt",bd.toString());
accInfo.put("huifu_id", jkStore.getHuifuId());
accInfo.put("percentage_div", "100.00");
acct_infos.add(accInfo);
acct_split_bunch.put("acct_infos", acct_infos);
acct_split_bunch.put("percentage_flag", "Y");
data.put("acct_split_bunch", acct_split_bunch);
String sign = HuifuSignUtil.sign(data, vendorProperties.getPrivateKey());
JSONObject commonParams = new JSONObject();
commonParams.put("sys_id", vendorProperties.getSysId());
commonParams.put("product_id", vendorProperties.getProductId());
commonParams.put("sign", sign);
commonParams.put("data", data);
log.info("参数:{}", commonParams);
String res = HttpUtil.doPostJson(YUE_PAY_API, null, commonParams);
log.info("余额支付接口返回数据:{}", res);
JSONObject response = JSONObject.parseObject(res);
if (response == null) {
return new CashOutResponse(300,"提现失败!");
}
JSONObject responseData = response.getJSONObject("data");
String resp_code = responseData.getString("resp_code");
String req_seq_id = responseData.getString("req_seq_id");
String req_date = responseData.getString("req_date");
String out_huifu_id = responseData.getString("out_huifu_id");
String ord_amt = responseData.getString("ord_amt");
if ("00000000".equals(resp_code)) {
log.info("提现成功,流水号:{}", snowId);
CashOutRecord cashOutRecord = new CashOutRecord();
cashOutRecord.setAmount(bd);
cashOutRecord.setOutHuifuId(vendorProperties.getHuifuId());
cashOutRecord.setStoreId(dto.getStoreId());
cashOutRecord.setCreateTime(LocalDateTime.now());
cashOutRecordMapper.insert(cashOutRecord);
BigDecimal supBalance = storeAccount.getBalance().subtract(bd).setScale(2, RoundingMode.DOWN);
BigDecimal supAvaBalance = storeAccount.getAvailableBalance().subtract(bd).setScale(2, RoundingMode.DOWN);
storeAccount.setBalance(supBalance);
storeAccount.setAvailableBalance(supAvaBalance);
storeAccountMapper.updateById(storeAccount);
return new CashOutResponse(200,"success");
}
throw new ServiceException(String.format("余额支付接口返回状态码:%s,提现失败!",resp_code));
}
/**
* 主动取现,从商户账户提现到银行卡
* https://paas.huifu.com/open/doc/api/#/jyjs/qx/api_qx
*/
public void realWithdrawCash(){
}
}
@@ -0,0 +1,7 @@
package cn.unizk.service;
import cn.unizk.entity.po.Combo;
import com.baomidou.mybatisplus.extension.service.IService;
public interface ComboService extends IService<Combo> {
}
@@ -0,0 +1,12 @@
package cn.unizk.service;
import cn.unizk.entity.po.Device;
import cn.unizk.entity.vo.DeviceVO;
import cn.unizk.entity.vo.UniPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
public interface DeviceService extends IService<Device> {
Page<Device> listPage(UniPage<DeviceVO> uniPage);
}
@@ -0,0 +1,105 @@
package cn.unizk.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author hao
* @Date 2025/11/3 18:36
* @Version 1.0
**/
@Service
@Slf4j
public class FileService {
private static final int CHUNK_SIZE = 1024;
public List<byte[]> splitFile(String filePath) throws IOException {
File file = new File(filePath);
return splitFile(file);
}
public List<byte[]> splitFileWithNIO(String filePath) throws IOException {
File file = new File(filePath);
Path path = file.toPath();
log.info("开始分包,文件路径: {}, 文件大小: {} 字节", filePath, file.length());
return splitFileWithNIO(path);
}
public List<byte[]> splitFile(File file) throws IOException {
List<byte[]> chunks = new ArrayList<>();
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[CHUNK_SIZE];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 如果读取的字节数不足1024,创建正确大小的数组
if (bytesRead < CHUNK_SIZE) {
byte[] lastChunk = new byte[bytesRead];
System.arraycopy(buffer, 0, lastChunk, 0, bytesRead);
chunks.add(lastChunk);
} else {
chunks.add(buffer.clone());
}
buffer = new byte[CHUNK_SIZE];
}
}
return chunks;
}
public List<byte[]> splitFileWithNIO(Path filePath) throws IOException {
List<byte[]> chunks = new ArrayList<>();
try (FileChannel fileChannel = FileChannel.open(filePath, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(CHUNK_SIZE);
int chunkIndex = 0;
while (fileChannel.read(buffer) != -1) {
buffer.flip(); // 切换为读模式
byte[] chunk = new byte[buffer.remaining()];
buffer.get(chunk);
log.info("第{}块,实际大小: {} 字节", chunkIndex, chunk.length);
chunks.add(chunk);
buffer.clear(); // 清空缓冲区,准备下一次读取
chunkIndex++;
}
}
log.info("分包完成,总块数: {}", chunks.size());
int lastIndex = chunks.size() - 1;
byte[] lastPackage = chunks.get(lastIndex);
if (lastPackage.length < 1024) {
// 最后一个包不足1024字节,用0xFF填充
byte[] paddedPackage = new byte[1024];
System.arraycopy(lastPackage, 0, paddedPackage, 0, lastPackage.length);
Arrays.fill(paddedPackage, lastPackage.length, 1024, (byte) 0xFF);
chunks.set(lastIndex, paddedPackage);
log.info("最后一个包从{}字节填充到1024字节", lastPackage.length);
}
// 打印每块的大小
/*for (int i = 0; i < chunks.size(); i++) {
log.debug("块{}: {}字节", i, chunks.get(i).length);
}*/
return chunks;
}
}
@@ -0,0 +1,68 @@
package cn.unizk.service;
import cn.unizk.entity.dto.PrDTO;
import cn.unizk.entity.po.MobileAppUser;
import cn.unizk.entity.po.TreatParameter;
import cn.unizk.mapper.MobileAppUserMapper;
import cn.unizk.mapper.TreatParameterMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @Author hao
* @Date 2025/12/18 17:00
* @Version 1.0
**/
@Service
public class GeneratePrService {
@Resource
private MobileAppUserMapper mobileAppUserMapper;
@Resource
private TreatParameterMapper treatParameterMapper;
public PrDTO generatePr(Integer userId){
if (userId == null){
return null;
}
MobileAppUser user = mobileAppUserMapper.selectById(userId);
if (user == null || StringUtils.isBlank(user.getPainNumber())){
return null;
}
PrDTO prDTO = new PrDTO();
String uuid = RandomStringUtils.randomNumeric(16);
prDTO.setId(uuid);
List<PrDTO.PrItem> prItems = new ArrayList<>();
String painNumber = user.getPainNumber();
if (painNumber.contains(",")){
String[] split = painNumber.split(",");
for (String s : split) {
PrDTO.PrItem prItem = fill(s);
prItems.add(prItem);
}
prDTO.setPrItems(prItems);
}else {
PrDTO.PrItem prItem = fill(painNumber);
prItems.add(prItem);
prDTO.setPrItems(prItems);
}
return prDTO;
}
private PrDTO.PrItem fill(String painNumber){
TreatParameter treatParameter = treatParameterMapper.selectById(Integer.parseInt(painNumber));
PrDTO.PrItem prItem = new PrDTO.PrItem();
prItem.setFr(treatParameter.getFr());
prItem.setPl(treatParameter.getPl());
prItem.setPw(treatParameter.getPw());
prItem.setUl(treatParameter.getUl());
prItem.setTl(treatParameter.getTl());
return prItem;
}
}
@@ -0,0 +1,9 @@
package cn.unizk.service;
import cn.unizk.entity.vo.SysUserVO;
import cn.unizk.entity.vo.SysUserVO2;
public interface LoginService {
SysUserVO loginByUsername(SysUserVO sysUserVO);
}
@@ -0,0 +1,14 @@
package cn.unizk.service;
import cn.unizk.entity.po.Menu;
import cn.unizk.entity.vo.TreeMenu;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface MenuService extends IService<Menu> {
List<TreeMenu> getAllMenus();
List<TreeMenu> getStoreMenus();
}
@@ -0,0 +1,34 @@
package cn.unizk.service;
import cn.unizk.entity.po.SysUser;
import cn.unizk.exception.ServiceException;
import cn.unizk.security.context.AuthenticationContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @Author hao
* @Date 2025/8/25 14:47
* @Version 1.0
**/
@Service
@Slf4j
public class PasswordService {
public void validate(SysUser sysUser){
Authentication context = AuthenticationContextHolder.getContext();
String rawPassword = (String) context.getCredentials();
if (!matchPwd(sysUser,rawPassword)){
throw new ServiceException("密码错误!");
}
}
public boolean matchPwd(SysUser sysUser,String rawPassword){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode(rawPassword);
log.info("encode={}",encode);
return bCryptPasswordEncoder.matches(rawPassword,sysUser.getPassword());
}
}
@@ -0,0 +1,7 @@
package cn.unizk.service;
import cn.unizk.entity.po.JkPhysio;
import com.baomidou.mybatisplus.extension.service.IService;
public interface PhysioService extends IService<JkPhysio> {
}
@@ -0,0 +1,7 @@
package cn.unizk.service;
import cn.unizk.entity.po.ProductCarousel;
import com.baomidou.mybatisplus.extension.service.IService;
public interface ProductCarouselService extends IService<ProductCarousel> {
}
@@ -0,0 +1,10 @@
package cn.unizk.service;
import cn.unizk.entity.po.JkProduct;
import cn.unizk.entity.vo.JkProductVO;
import com.baomidou.mybatisplus.extension.service.IService;
public interface ProductService extends IService<JkProduct> {
JkProductVO echo(Integer id);
}
@@ -0,0 +1,7 @@
package cn.unizk.service;
import cn.unizk.entity.po.JkStore;
import com.baomidou.mybatisplus.extension.service.IService;
public interface StoreService extends IService<JkStore> {
}
@@ -0,0 +1,7 @@
package cn.unizk.service;
import cn.unizk.entity.po.SysDict;
import com.baomidou.mybatisplus.extension.service.IService;
public interface SysDictService extends IService<SysDict> {
}
@@ -0,0 +1,16 @@
package cn.unizk.service;
import cn.unizk.entity.po.SysFile;
import cn.unizk.entity.vo.MinioRespVO;
import cn.unizk.entity.vo.ResultData;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
public interface SysFileService extends IService<SysFile> {
ResultData<MinioRespVO> simpleUpload(MultipartFile file, String bucketName);
void deleteFile(String bucketName,String fileName) throws Exception;
void deleteFileById(Integer fileId) throws Exception;
}
@@ -0,0 +1,22 @@
package cn.unizk.service;
import cn.unizk.entity.po.MobileOrder;
import cn.unizk.entity.vo.MobileOrderVO;
import cn.unizk.entity.vo.OrderReqVO;
import cn.unizk.entity.vo.ResultData;
import cn.unizk.entity.vo.UniPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
public interface SysOrderService extends IService<MobileOrder> {
Page<MobileOrderVO> orderList(UniPage<OrderReqVO> uniPage);
MobileOrder getOrderByIdAndPhone(String orderId,String phone);
ResultData refund(MobileOrder wxOrder);
Page<MobileOrderVO> afterSale(UniPage<OrderReqVO> uniPage);
ResultData agreeRefund(String orderId,String platformType);
}
@@ -0,0 +1,7 @@
package cn.unizk.service;
import cn.unizk.entity.po.SysUser;
import com.baomidou.mybatisplus.extension.service.IService;
public interface SysUserService extends IService<SysUser> {
}
@@ -0,0 +1,139 @@
package cn.unizk.service;
import cn.unizk.constants.CacheConstant;
import cn.unizk.constants.Constants;
import cn.unizk.entity.dto.LoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @Author hao
* @Date 2025/8/22 18:47
* @Version 1.0
**/
@Service
@Slf4j
public class TokenService {
@Autowired
@Qualifier("kryoRedisTemplate")
private RedisTemplate redisTemplate;
@Value("${token.header}")
private String tokenHeader;
private SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
@Value("${token.expireTime}")
private Integer expireTime;
private static final long TWENTY_MINUTES = 20 * 60 * 1000L;
private static final long MINUTE_CAST_MILLISECOND_UNIT = 60 * 1000L;
public LoginUser getLoginUser(HttpServletRequest request){
String token = getToken(request);
if (StringUtils.isNotBlank(token)){
try {
Claims claims = parseToken(token);
String uuid = (String) claims.get(Constants.JWT_TOKEN_KEY);
String redisTokenKey = getRedisTokenKey(uuid);
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(redisTokenKey);
if (Objects.isNull(loginUser)){
return loginUser;
}
verifyToken(loginUser);
return loginUser;
//在这里调用verifyToken(),不然1小时会过期
}catch (Exception e){
// e.printStackTrace();
log.info("token解析异常");
}
}
return null;
}
public String createToken(LoginUser user) {
String uuid = RandomStringUtils.randomAlphanumeric(16);
user.setToken(uuid);
user.setLoginTime(System.currentTimeMillis());
refreshToken(user, true);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.JWT_TOKEN_KEY, uuid);
return createToken(claims);
}
private String createToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.signWith(secretKey)
.compact();
}
private Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}
public void verifyToken(LoginUser user) {
Long expireTime = user.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= TWENTY_MINUTES) {
refreshToken(user, false);
}
}
private void refreshToken(LoginUser user, Boolean refreshLoginTime) {
long currentTimeMillis = System.currentTimeMillis();
if (refreshLoginTime) {
user.setLoginTime(currentTimeMillis);
}
user.setExpireTime(currentTimeMillis + expireTime * MINUTE_CAST_MILLISECOND_UNIT);
String tokenKey = getRedisTokenKey(user.getToken());
redisTemplate.opsForValue().set(tokenKey, user,expireTime, TimeUnit.MINUTES);
}
private String getRedisTokenKey(String uuid) {
return CacheConstant.LOGIN_TOKEN_KEY + uuid;
}
public String getToken(HttpServletRequest request){
String token = request.getHeader(tokenHeader);
if (StringUtils.isNotBlank(token) && token.startsWith(Constants.TOKEN_PREFIX)){
token = token.replace(Constants.TOKEN_PREFIX,"");
}
return token;
}
public void delLoginUser(String token){
if (StringUtils.isNotBlank(token)){
String redisTokenKey = getRedisTokenKey(token);
redisTemplate.delete(redisTokenKey);
}
}
}
@@ -0,0 +1,60 @@
package cn.unizk.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
/**
* @Author hao
* @Date 2025/9/10 16:54
* @Version 1.0
**/
@Service
@Slf4j
public class UdpClientService {
/**
* 向指定设备发送UDP指令
* @param deviceIp 设备IP地址
* @param devicePort 设备端口
* @param command 要发送的指令
* @return 发送是否成功
*/
public boolean sendCommand(String deviceIp, int devicePort, String command) {
try (DatagramSocket clientSocket = new DatagramSocket(9999)) { // 创建临时socket
InetAddress address = InetAddress.getByName(deviceIp);
byte[] sendData = command.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
address,
devicePort
);
clientSocket.setSoTimeout(3000);
clientSocket.send(sendPacket);
System.out.printf("Command sent to %s:%d - %s%n", deviceIp, devicePort, command);
return true;
} catch (IOException e) {
System.err.printf("Failed to send command to %s:%d: %s%n",
deviceIp, devicePort, e.getMessage());
return false;
}
}
public boolean sendCommand(UdpServerService.DeviceInfo deviceInfo, String command) {
if (deviceInfo == null) {
System.err.println("DeviceInfo is null, cannot send command.");
return false;
}
return sendCommand(deviceInfo.getIp(), deviceInfo.getPort(), command);
}
}
@@ -0,0 +1,718 @@
package cn.unizk.service;
import cn.hutool.core.date.DateUtil;
import cn.unizk.constant.UniConstant;
import cn.unizk.entity.po.Device;
import cn.unizk.entity.po.DeviceUpgrade;
import cn.unizk.entity.po.DeviceUsageDetail;
import cn.unizk.entity.po.DeviceUsageRecord;
import cn.unizk.mapper.DeviceMapper;
import cn.unizk.mapper.DeviceUpgradeMapper;
import cn.unizk.mapper.DeviceUsageDetailMapper;
import cn.unizk.mapper.DeviceUsageRecordMapper;
import cn.unizk.task.DeviceTask;
import cn.unizk.utils.XorUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author hao
* @Date 2025/9/10 16:17
* @Version 1.0
**/
//@Service
@Slf4j
public class UdpServerService {
private ConcurrentHashMap<String, DeviceInfo> deviceMap = new ConcurrentHashMap<>();
private DatagramSocket serverSocket;
private volatile boolean running = false;
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private static final int LISTEN_PORT = 61100;
private static final long SESSION_TIMEOUT = 60_000;
@Resource
private UdpClientService udpClientService;
@Resource
private DeviceMapper deviceMapper;
@Resource
private DeviceUsageRecordMapper deviceUsageRecordMapper;
@Resource
private DeviceTask deviceTask;
@Resource
private DeviceUsageDetailMapper usageDetailMapper;
@Resource
private DeviceUpgradeMapper upgradeMapper;
@Resource
private FileService fileService;
@Resource(name = "kryoRedisTemplate")
private RedisTemplate redisTemplate;
private static final String UPGRADE_PACKAGE_ROOT_PATH = "/opt/jkadmin/upgrade/";
@Getter
@Setter
public static class DeviceInfo {
private String muid;
private String status;
private String ip;
private int port;
private Boolean startCount = false;
private final long lastActiveTime;
// DeviceInfo(){}
/*public boolean getStartCount(){
return this.startCount;
}*/
DeviceInfo(String muid, String status, String ip, int port){
this.muid = muid;
this.status = status;
this.ip = ip;
this.port = port;
this.startCount = false;
this.lastActiveTime = System.currentTimeMillis();
}
DeviceInfo(String muid, String status, String ip, int port,boolean startCount){
this.muid = muid;
this.status = status;
this.ip = ip;
this.port = port;
this.startCount = startCount;
this.lastActiveTime = System.currentTimeMillis();
}
}
@PostConstruct
public void startServer() {
try {
serverSocket = new DatagramSocket(LISTEN_PORT);
running = true;
log.info("UDP Server started on port: {}",LISTEN_PORT);
executorService.execute(this::receivePackets);
} catch (SocketException e) {
log.error("Failed to start UDP server on port: {}" ,LISTEN_PORT , e);
}
}
@PreDestroy
public void stopServer() {
running = false;
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
executorService.shutdown();
log.info("UDP Server stopped.");
}
private void receivePackets() {
byte[] receiveBuffer = new byte[1024]; // 缓冲区
log.info("持续接收UDP数据包,{}",serverSocket.isClosed());
while (running && !serverSocket.isClosed()) {
try {
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
serverSocket.receive(receivePacket); // 阻塞等待接收数据
log.info("处理接收到的数据包");
// 处理接收到的数据包(可以在新线程中处理,避免阻塞接收)
handlePacket(receivePacket);
} catch (Exception e) {
if (running) { // 只有在运行状态下的异常才打印
log.error("Error receiving packet: " , e);
}
}
}
}
private void handlePacket(DatagramPacket packet) {
String deviceIp = packet.getAddress().getHostAddress();
int devicePort = packet.getPort();
String receivedMessage = new String(packet.getData(), 0, packet.getLength());
System.out.printf("Received from %s:%d - Message: %s%n",
deviceIp, devicePort, receivedMessage);
if (StringUtils.isBlank(receivedMessage)){
log.info("接收到的数据包为空");
return;
}
// 需要根据实际协议格式进行解析
String muid = parsemuid(receivedMessage); // 需要实现此方法
String status = receivedMessage.substring(2, 3);
log.info("解析得到的muid={}",muid);
log.info("解析得到的状态码是:{}",status);
if (muid != null && !muid.isEmpty()) {
Device device = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
// 将设备信息存入Map
System.out.println("Device registered: " + muid + " at " + deviceIp + ":" + devicePort);
if (Objects.isNull(device)){
log.info("数据库不存在该设备,muid={}",muid);
}else {
DeviceInfo deviceInfo = deviceMap.get(muid);
if ("1".equals(status)){
if (Objects.isNull(deviceInfo)){
log.info("deviceMap缓存中没有{}这个设备",muid);
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
}else {
if (deviceInfo.getStartCount() && device.getSurplus() > 0){
log.info("设备状态为01开机,开始计数startCount为true");
int count = device.getSurplus() - 1;
device.setSurplus(count);
device.setExpireTime(String.valueOf(count));
deviceMapper.updateById(device);
}
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
}
String lo = null, la = null;
String[] items = receivedMessage.split("\\|");
for (String item : items) {
if (item.startsWith("LO")){
lo = item.substring(2);
}
if (item.startsWith("LA")){
la = item.substring(2);
}
}
if (lo != null && la != null){
device.setLo(lo);
device.setLa(la);
deviceMapper.updateById(device);
}
parseDeviceInfo(device,receivedMessage);
/**
* 拿device的硬件版本和软件版本去查device_upgrade表,如果跟当前版本不一致则升级
*/
if ("000B57020457D4B100001F98".equals(muid)){
log.info("muid=***F97,回复升级心跳");
replyUpgradeHeartbeat(deviceMap.get(muid),packet);
}else {
replyHeartbeat(deviceMap.get(muid),packet);
}
DeviceUsageDetail detail = new DeviceUsageDetail();
detail.setStatus(status);
recordUse(device,detail);
if (device.getOperatingStatus() == 0){
changeDeviceOperatingStatus(device.getDeviceId(),2);
}
}
if ("1".equals(status) && device.getSurplus() < 1){
log.info("设备状态为1,无剩余使用次数,马上停");
String c1 = "$01|MU{uid}|DT{date}|ON{1}|CF1|WM1|TC000|*{CRC}\r\n";
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c1.replace("{uid}", muid).replace("{date}", now).replace("{1}", "0");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String s = replace.replace("{CRC}", asciiHex);
byte[] sendData = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
deviceMapper.updateStatus(device.getDeviceId(),"0");
}
if ("2".equals(status)){
updateLastRunTime(device);
String mo = parseMo(device, receivedMessage);
if (device.getSurplus() < 1) {
log.info("设备状态为2,五剩余使用次数,马上听");
//马上停
String c2 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c2.replace("{uid}", muid)
.replace("{date}", now).replace("{1}", "0").replace("{2}", "000");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String s = replace.replace("{CRC}", asciiHex);
byte[] sendData = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
deviceMapper.updateStatus(device.getDeviceId(),"0");
}else {
try {
Device dvie = toLiliao(device.getDeviceId());
DeviceUsageDetail detail = parseMsgToDetail(receivedMessage);
detail.setStatus(status);
recordUse(dvie,detail);
}catch (Exception e){
log.error("记录设备使用发生异常",e);
}
if (device.getPaymentMethod() == 1){
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
}else {
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort,true));
}
replyHeartbeat(deviceMap.get(muid),packet);
}
}
if (Objects.isNull(deviceInfo)){
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
deviceInfo = deviceMap.get(muid);
}
if ("4".equals(status) && deviceInfo.getStartCount()){
log.info("设备状态为4,关机,并且startCount为true");
int count = device.getSurplus() - 1;
device.setSurplus(count);
device.setExpireTime(String.valueOf(count));
deviceMapper.updateById(device);
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
}
if ("4".equals(status)){
try {
changeDeviceOperatingStatus(device.getDeviceId(),1);
updateRecord(device.getDeviceId());
}catch (Exception e){
log.error("更新设备使用记录发生异常",e);
}
}
}
}
//马上回
/*log.info("马上回------");
// udpClientService.sendCommand(deviceIp,devicePort,"$01|MU000B57020457D4B100001F97|DT20251011163826|ON0|CF1|WM1|TC000|*7E\r\n");
InetAddress address = null;
try {
address = InetAddress.getByName(deviceIp);
} catch (UnknownHostException e) {
e.printStackTrace();
}
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF1|WM1|TC000|*{CRC}\r\n";
String c02 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
String command = "$01|MU000B57020457D4B100001F97|DT20251011163826|ON0|CF1|WM1|TC000|*7E\r\n";
if ("1".equals(status)){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", "0");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
command = replace.replace("{CRC}", asciiHex);
}
if ("2".equals(status)){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c02.replace("{uid}", muid)
.replace("{date}", now).replace("{1}", "0").replace("{2}", "000");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
command = replace.replace("{CRC}", asciiHex);
}
byte[] sendData = command.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}*/
}
private void replyHeartbeat(DeviceInfo info,DatagramPacket packet){
if (Objects.isNull(info)){
log.info("method->replyHeartbeat,DeviceInfo is null");
return;
}
String status = info.getStatus();
String muid = info.getMuid();
switch (status){
case "1":
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|WM1|TC000|*{CRC}\r\n";
Device device = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", device.getStatus()).replace("{2}",device.getCf());
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String message = replace.replace("{CRC}", asciiHex);
byte[] sendData = message.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
break;
case "2":
String c02 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
Device device2 = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
String now2 = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace2 = c02.replace("{uid}", muid)
.replace("{date}", now2).replace("{1}", device2.getStatus()).replace("{2}", "000");
String crc2 = replace2.substring(1, replace2.lastIndexOf("*"));
String asciiHex2 = XorUtil.xorAsciiHex(crc2);
String msg = replace2.replace("{CRC}", asciiHex2);
byte[] sendData2 = msg.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket2 = new DatagramPacket(
sendData2,
sendData2.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket2);
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
private void parseDeviceInfo(Device device,String msg){
String[] items = msg.split("\\|");
String hw = null,sw = null,ic = null,cf = null,wm = null,cr = null;
for (String item : items) {
if (item.startsWith("HW")){
hw = item.substring(2);
}
if (item.startsWith("SW")){
sw = item.substring(2);
}
if (item.startsWith("IC")){
ic = item.substring(2);
}
if (item.startsWith("CF")){
cf = item.substring(2);
}
if (item.startsWith("WM")){
wm = item.substring(2);
}
if (item.startsWith("CR")){
cr = item.substring(2);
}
}
device.setHw(hw);
device.setSw(sw);
device.setIccid(ic);
if (StringUtils.isBlank(device.getCf())){
device.setCf(cf);
}
device.setWm(wm);
device.setCr(cr);
device.setOperatingStatus(3);
device.setLastRunTime(LocalDateTime.now());
log.info("服务器上的LocalDateTime.now()========={}",LocalDateTime.now());
deviceMapper.updateDeviceInfo(device);
}
private String parseMo(Device device,String msg){
String[] items = msg.split("\\|");
String mo = null;
for (String item : items) {
if (item.startsWith("MO")){
mo = item.substring(2);
break;
}
}
device.setMo(mo);
Device dv = new Device();
dv.setDeviceId(device.getDeviceId());
dv.setMo(mo);
deviceMapper.updateById(dv);
return mo;
}
private DeviceUsageDetail parseMsgToDetail(String msg){
String[] items = msg.split("\\|");
String fr = null,pw = null,pl = null,ul = null,wt = null;
for (String item : items) {
if (item.startsWith("FR")){
fr = item.substring(2);
}
if (item.startsWith("PW")){
pw = item.substring(2);
}
if (item.startsWith("PL")){
pl = item.substring(2);
}
if (item.startsWith("UL")){
ul = item.substring(2);
}
if (item.startsWith("WT")){
wt = item.substring(2);
}
}
DeviceUsageDetail detail = new DeviceUsageDetail();
detail.setFr(fr);
detail.setPw(pw);
detail.setPl(pl);
detail.setUl(ul);
detail.setWt(wt);
return detail;
}
private void updateLastRunTime(Device device){
Device d = new Device();
d.setDeviceId(device.getDeviceId());
d.setLastRunTime(LocalDateTime.now());
deviceMapper.updateById(d);
}
private Device toLiliao(String deviceId){
Device device = deviceMapper.selectById(deviceId);
if (Objects.nonNull(device) && device.getOperatingStatus() != 0){
device.setOperatingStatus(0);
deviceMapper.updateById(device);
deviceTask.addAbnormalDeviceTask(deviceId);
}
return device;
}
private void recordUse(Device device, DeviceUsageDetail detail){
if (Objects.isNull(device)){
log.info("设备使用记录recordUse->device is null");
return;
}
device.setLastRunTime(LocalDateTime.now());
deviceMapper.updateById(device);
DeviceUsageRecord record = deviceUsageRecordMapper.selectOne(new LambdaQueryWrapper<DeviceUsageRecord>()
.eq(DeviceUsageRecord::getDeviceId, device.getDeviceId())
.eq(DeviceUsageRecord::getStartUseFlag, "true"));
if (Objects.nonNull(record)){
if ("2".equals(detail.getStatus())){
detail.setUsageRecordId(record.getId());
detail.setCreateTime(LocalDateTime.now());
usageDetailMapper.insert(detail);
}
log.info("设备正在运行");
return;
}
DeviceUsageRecord deviceUsageRecord = new DeviceUsageRecord();
deviceUsageRecord.setStartTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceUsageRecord.setDeviceId(device.getDeviceId());
deviceUsageRecord.setStartUseFlag(UniConstant.CONST_TRUE);
deviceUsageRecord.setWm(device.getWm());
deviceUsageRecord.setBelong(device.getAffiliatedWith());
deviceUsageRecord.setMuid(device.getMuid());
deviceUsageRecordMapper.insert(deviceUsageRecord);
detail.setCreateTime(LocalDateTime.now());
detail.setUsageRecordId(deviceUsageRecord.getId());
/*
如果真要记录待机状态,那么把status=1的recordUse()放开,在这里加上根据deviceUsageRecord.getId()和status=1去查DeviceUsageDetail表,
如果不为空则不插入数据,即只记录一条状态为待机的数据
*/
List<DeviceUsageDetail> usageDetails = usageDetailMapper.selectList(new LambdaQueryWrapper<DeviceUsageDetail>().eq(DeviceUsageDetail::getUsageRecordId, deviceUsageRecord.getId())
.eq(DeviceUsageDetail::getStatus, "1"));
if (CollectionUtils.isEmpty(usageDetails)){
usageDetailMapper.insert(detail);
}
}
private void updateRecord(String deviceId){
DeviceUsageRecord deviceUsageRecord = deviceUsageRecordMapper.selectOne(new LambdaQueryWrapper<DeviceUsageRecord>()
.eq(DeviceUsageRecord::getDeviceId, deviceId)
.eq(DeviceUsageRecord::getStartUseFlag, UniConstant.CONST_TRUE));
if (Objects.isNull(deviceUsageRecord)){
return;
}
deviceUsageRecord.setEndTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceUsageRecord.setStartUseFlag(UniConstant.CONST_FALSE);
deviceUsageRecordMapper.updateById(deviceUsageRecord);
DeviceUsageDetail detail = new DeviceUsageDetail();
detail.setStatus("4");
detail.setCreateTime(LocalDateTime.now());
detail.setUsageRecordId(deviceUsageRecord.getId());
usageDetailMapper.insert(detail);
}
private void changeDeviceOperatingStatus(String deviceId,Integer status){
Device device = deviceMapper.selectById(deviceId);
if (Objects.isNull(device)){
return;
}
device.setOperatingStatus(status);
deviceMapper.updateById(device);
}
public boolean reply(Device device,String status){
String muid = device.getMuid();
DeviceInfo deviceInfo = getDeviceInfo(muid);
if (deviceInfo == null){
log.info("设备会话已过期,请设备先上报一次数据");
return false;
}
String message = "";
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|WM1|TC000|*{CRC}\r\n";
String c02 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
if ("1".equals(deviceInfo.getStatus())){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", status).replace("{2}",device.getCf());
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
message = replace.replace("{CRC}", asciiHex);
}else if ("2".equals(deviceInfo.getStatus())){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c02.replace("{uid}", muid)
.replace("{date}", now).replace("{1}", status).replace("{2}", "000");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
message = replace.replace("{CRC}", asciiHex);
}else if ("4".equals(deviceInfo.getStatus())){
log.info("设备为关机状态");
return true;
}
try {
log.info("最终发送的数据为:{}",message);
byte[] data = message.getBytes(StandardCharsets.UTF_8);
InetAddress address = InetAddress.getByName(deviceInfo.getIp());
DatagramPacket packet = new DatagramPacket(data, data.length, address, deviceInfo.getPort());
serverSocket.send(packet);
System.out.printf("UDP Reply sent to %s:%d -> %s%n", deviceInfo.getIp(), deviceInfo.getPort(), message);
device.setStatus(status);
deviceMapper.updateById(device);
return true;
} catch (IOException e) {
log.info("回复设备UDP消息发送异常");
e.printStackTrace();
return true;
}
}
/**
* 根据实际协议解析设备ID
* 这是一个示例,您需要根据硬件数据包的实际格式重写此方法
* @param message 收到的原始消息
* @return 解析出的设备ID
*/
private String parsemuid(String message) {
Pattern p = Pattern.compile("^[^|]*\\|([^|]+)\\|");
Matcher matcher = p.matcher(message);
if (matcher.find()){
return matcher.group(1).substring(2);
}
// 如果协议里没有ID,也可以用IP:端口作为唯一标识
return null;
}
/**
* 获取设备信息
* @param muid 设备ID
* @return 设备信息,如果不存在则返回null
*/
public DeviceInfo getDeviceInfo(String muid) {
DeviceInfo deviceInfo = deviceMap.get(muid);
if (Objects.nonNull(deviceInfo) && System.currentTimeMillis() - deviceInfo.getLastActiveTime() < SESSION_TIMEOUT){
return deviceInfo;
}else {
if (Objects.isNull(deviceInfo)){
deviceMap.remove(muid);
return null;
}
if (!deviceInfo.getStartCount()){
deviceMap.remove(muid);
}
return null;
}
}
/**
* 获取所有在线设备
*/
public ConcurrentHashMap<String, DeviceInfo> getOnlineDevices() {
return new ConcurrentHashMap<>(deviceMap);
}
private void replyUpgradeHeartbeat(DeviceInfo info,DatagramPacket packet){
if (Objects.isNull(info)){
log.info("method->replyHeartbeat,DeviceInfo is null");
return;
}
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|UP{3}|WM1|TC000|*{CRC}\r\n";
/*
查device_upgrade表拿到当前版本升级包名称,然后去/opt/jkadmin/upgrade下面拿到升级包
调fileService拆包,得到包数量,替换{3},然后回复
*/
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getBeCurrentVersion, 1));
if (Objects.isNull(deviceUpgrade)){
replyHeartbeat(info,packet);
}else {
String fileUrl = UPGRADE_PACKAGE_ROOT_PATH + deviceUpgrade.getFileName();
List<byte[]> packages = null;
try {
packages = fileService.splitFileWithNIO(fileUrl);
} catch (IOException e) {
log.error("拆包发生异常-.-.-.-.-.-.-.-.-.",e);
}
if (CollectionUtils.isNotEmpty(packages) && packages.size() <= 32){
String muid = info.getMuid();
Device device = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
if (Objects.isNull(device)){
return;
}
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String r = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", device.getStatus())
.replace("{2}", device.getCf()).replace("{3}", String.valueOf(packages.size()));
String crc = r.substring(1, r.lastIndexOf("*"));
String xorAsciiHex = XorUtil.xorAsciiHex(crc);
String msg = r.replace("{CRC}", xorAsciiHex);
log.info("回复升级心跳数据:{}",msg);
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(bytes,bytes.length,packet.getSocketAddress());
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
log.error("回复升级心跳包发生异常",e);
}
}else {
log.info("升级包为空或升级包大小超过1024*32");
replyHeartbeat(info, packet);
}
}
}
}
@@ -0,0 +1,920 @@
package cn.unizk.service;
import cn.hutool.core.date.DateUtil;
import cn.unizk.constant.UniConstant;
import cn.unizk.entity.dto.PrDTO;
import cn.unizk.entity.po.*;
import cn.unizk.mapper.*;
import cn.unizk.task.DeviceTask;
import cn.unizk.utils.XorUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author hao
* @Date 2025/9/10 16:17
* @Version 1.0
**/
@Service
@Slf4j
public class UdpServerService2 {
// 存储设备地址的Map,Key可以是设备ID,Value是设备的IP和端口
private ConcurrentHashMap<String, DeviceInfo> deviceMap = new ConcurrentHashMap<>();
private DatagramSocket serverSocket;
private volatile boolean running = false;
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private static final int LISTEN_PORT = 61100; // 监听的端口
private static final long SESSION_TIMEOUT = 60_000;
@Resource
private UdpClientService udpClientService;
@Resource
private DeviceMapper deviceMapper;
@Resource
private DeviceUsageRecordMapper deviceUsageRecordMapper;
@Resource
private DeviceTask deviceTask;
@Resource
private DeviceUsageDetailMapper usageDetailMapper;
@Resource
private DeviceUpgradeMapper upgradeMapper;
@Resource
private FileService fileService;
@Resource(name = "kryoRedisTemplate")
private RedisTemplate redisTemplate;
@Resource
private GeneratePrService generatePrService;
@Resource
private UserTreatmentParametersMapper userTreatmentParametersMapper;
private static final String UPGRADE_PACKAGE_ROOT_PATH = "/opt/jkadmin/upgrade/";
@Getter
@Setter
public static class DeviceInfo {
private String muid;
private String status;
private String ip;
private int port;
private Boolean startCount = false;
private final long lastActiveTime;
// DeviceInfo(){}
/*public boolean getStartCount(){
return this.startCount;
}*/
DeviceInfo(String muid, String status, String ip, int port) {
this.muid = muid;
this.status = status;
this.ip = ip;
this.port = port;
this.startCount = false;
this.lastActiveTime = System.currentTimeMillis();
}
DeviceInfo(String muid, String status, String ip, int port, boolean startCount) {
this.muid = muid;
this.status = status;
this.ip = ip;
this.port = port;
this.startCount = startCount;
this.lastActiveTime = System.currentTimeMillis();
}
}
@PostConstruct
public void startServer() {
try {
serverSocket = new DatagramSocket(LISTEN_PORT);
running = true;
log.info("UDP Server started on port: {}", LISTEN_PORT);
// 启动一个线程来持续接收消息
executorService.execute(this::receivePackets);
} catch (SocketException e) {
log.error("Failed to start UDP server on port: {}", LISTEN_PORT, e);
}
}
@PreDestroy
public void stopServer() {
running = false;
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
executorService.shutdown();
log.info("UDP Server stopped.");
}
/**
* 持续接收UDP数据包的方法
*/
private void receivePackets() {
byte[] receiveBuffer = new byte[1024]; // 缓冲区
log.info("持续接收UDP数据包,{}", serverSocket.isClosed());
while (running && !serverSocket.isClosed()) {
try {
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
serverSocket.receive(receivePacket); // 阻塞等待接收数据
log.info("处理接收到的数据包");
// 处理接收到的数据包(可以在新线程中处理,避免阻塞接收)
handlePacket(receivePacket);
} catch (Exception e) {
if (running) { // 只有在运行状态下的异常才打印
log.error("Error receiving packet: ", e);
}
}
}
}
/**
* 处理接收到的UDP数据包
*
* @param packet 接收到的数据包
*/
private void handlePacket(DatagramPacket packet) {
// 获取发送方的IP和端口
String deviceIp = packet.getAddress().getHostAddress();
int devicePort = packet.getPort();
// 获取数据内容并转换为字符串
String receivedMessage = new String(packet.getData(), 0, packet.getLength());
log.info("Received from {}:{} - Message: {}",
deviceIp, devicePort, receivedMessage);
if (StringUtils.isBlank(receivedMessage)) {
log.info("接收到的数据包为空");
return;
}
// 关键步骤:解析数据包,获取设备ID并注册设备地址
// 假设数据包格式为: "muid:XXXXX;Status:Online;"
// 您需要根据硬件同事提供的实际协议格式进行解析
String muid = parsemuid(receivedMessage); // 需要实现此方法
String status = receivedMessage.substring(2, 3);
String status2 = receivedMessage.substring(1, 3);
if ("30".equals(status2)) {
status = "9";
}
log.info("解析得到的muid={}", muid);
log.info("解析得到的状态码是:{}", status);
if (muid != null && !muid.isEmpty()) {
Device device = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
// 将设备信息存入Map
System.out.println("Device registered: " + muid + " at " + deviceIp + ":" + devicePort);
if (Objects.isNull(device)) {
log.info("数据库不存在该设备,muid={}", muid);
} else {
DeviceInfo deviceInfo = deviceMap.get(muid);
if ("1".equals(status)) {
if (Objects.isNull(deviceInfo)) {
log.info("deviceMap缓存中没有{}这个设备", muid);
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort));
} else {
/*if (deviceInfo.getStartCount() && device.getSurplus() > 0 && device.getPaymentMethod() != null && device.getPaymentMethod() == 2){
log.info("设备状态为01开机,开始计数startCount为true");
int count = device.getSurplus() - 1;
device.setSurplus(count);
device.setExpireTime(String.valueOf(count));
deviceMapper.updateById(device);
}*/
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort));
}
String lo = null, la = null;
String[] items = receivedMessage.split("\\|");
for (String item : items) {
if (item.startsWith("LO")) {
lo = item.substring(2);
}
if (item.startsWith("LA")) {
la = item.substring(2);
}
}
if (lo != null && la != null) {
device.setLo(lo);
device.setLa(la);
deviceMapper.updateById(device);
}
parseDeviceInfo(device, receivedMessage);
/**
* 拿device的硬件版本和软件版本去查device_upgrade表,如果跟当前版本不一致则升级
*/
DeviceUpgrade upgrade = upgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getBeCurrentVersion, 1));
Device nDevice = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
if (Objects.nonNull(upgrade) && upgrade.getHVersion().equals(nDevice.getHw()) && !upgrade.getSVersion().equals(nDevice.getSw()) && nDevice.getUpgradeSwitch() == 1) {
log.info("回复升级心跳,回复升级心跳回复升级心跳,回复升级心跳,回复升级心跳");
replyUpgradeHeartbeat(deviceMap.get(muid), packet);
} else {
/*String o = (String) redisTemplate.opsForValue().get(UniConstant.REDIS_CURRENT_DEVICE_USER + device.getDeviceId());
PrDTO prDTO = generatePrService.generatePr(o);
PrDTO cachePrdto = (PrDTO) redisTemplate.opsForValue().get(o + device.getDeviceId());*/
replyHeartbeat(deviceMap.get(muid), packet, null, null);
}
DeviceUsageDetail detail = new DeviceUsageDetail();
detail.setStatus(status);
recordUse(device, detail);
if (device.getOperatingStatus() == 0) {
changeDeviceOperatingStatus(device.getDeviceId(), 2);
}
}
if ((("1".equals(status) && device.getSurplus() < 1) || device.getForcePay() == 1) && device.getFreeCount() < 1) {
log.info("设备状态为1,无剩余使用次数,马上停,强制付费");
String c1 = "$01|MU{uid}|DT{date}|ON{1}|CF1|WM1|TC000|*{CRC}\r\n";
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c1.replace("{uid}", muid).replace("{date}", now).replace("{1}", "0");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String s = replace.replace("{CRC}", asciiHex);
byte[] sendData = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
deviceMapper.updateStatus(device.getDeviceId(), "0");
}
if ("2".equals(status)) {
updateLastRunTime(device);
String mo = parseMo(device, receivedMessage);
if ((device.getSurplus() < 1 || device.getForcePay() == 1) && device.getFreeCount() < 1) {
log.info("设备状态为2,五剩余使用次数,马上听");
//马上停
String c2 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c2.replace("{uid}", muid)
.replace("{date}", now).replace("{1}", "0").replace("{2}", "000");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String s = replace.replace("{CRC}", asciiHex);
byte[] sendData = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort));
deviceMapper.updateStatus(device.getDeviceId(), "0");
} else {
try {
Device dvie = toLiliao(device.getDeviceId());
DeviceUsageDetail detail = parseMsgToDetail(receivedMessage);
detail.setStatus(status);
recordUse(dvie, detail);
} catch (Exception e) {
log.error("记录设备使用发生异常", e);
}
if (device.getPaymentMethod() == 1) {
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort));
} else {
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort, true));
}
replyHeartbeat(deviceMap.get(muid), packet, null, null);
}
}
if ("3".equals(status)) {
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort));
// String o = (String) redisTemplate.opsForValue().get(UniConstant.REDIS_CURRENT_DEVICE_USER + device.getDeviceId());
// PrDTO o1 = (PrDTO) redisTemplate.opsForValue().get(o + device.getDeviceId());
//openid是扫码开机的时候放到redis的
// String openid = (String) redisTemplate.opsForValue().get(UniConstant.DEVICE_ID + device.getDeviceId());
// String openid = "oATNGvnMbu6tbNgbnFZPWtJBGvFM";
Integer appUserId = 9;
PrDTO prDTO = generatePrService.generatePr(appUserId);
if (Objects.nonNull(prDTO)){
List<PrDTO.PrItem> prItems = prDTO.getPrItems();
if (CollectionUtils.isNotEmpty(prItems)){
List<UserTreatmentParameters> list = new ArrayList<>();
for (PrDTO.PrItem prItem : prItems) {
UserTreatmentParameters userTreatmentParameters = new UserTreatmentParameters();
userTreatmentParameters.setAppUserId(appUserId);
userTreatmentParameters.setDeviceId(device.getDeviceId());
userTreatmentParameters.setFr(prItem.getFr());
userTreatmentParameters.setPl(prItem.getPl());
userTreatmentParameters.setPw(prItem.getPw());
userTreatmentParameters.setUl(prItem.getUl());
userTreatmentParameters.setTl(prItem.getTl());
userTreatmentParameters.setCreateTime(LocalDateTime.now());
userTreatmentParameters.setStatus(1);
list.add(userTreatmentParameters);
}
List<UserTreatmentParameters> userTreatmentParametersList = userTreatmentParametersMapper.selectList(new LambdaQueryWrapper<UserTreatmentParameters>().eq(UserTreatmentParameters::getAppUserId, appUserId));
if (CollectionUtils.isEmpty(userTreatmentParametersList)){
userTreatmentParametersMapper.insert(list);
}
}
}
replyHeartbeat(deviceMap.get(muid), packet, prDTO, null);
}
if (Objects.isNull(deviceInfo)) {
deviceMap.put(muid, new DeviceInfo(muid, status, deviceIp, devicePort));
deviceInfo = deviceMap.get(muid);
}
if ("9".equals(status)) {
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
/*String o = (String) redisTemplate.opsForValue().get(UniConstant.REDIS_CURRENT_DEVICE_USER + device.getDeviceId());
PrDTO prDTO = (PrDTO) redisTemplate.opsForValue().get(o + device.getDeviceId());*/
String prId = null,tn = null;
String[] items = receivedMessage.split("\\|");
for (String item : items) {
if (item.startsWith("CI")){
prId = item.substring(2);
}
if (item.startsWith("TN")){
tn = item.substring(2);
}
}
PrDTO prDTO = prId == null ? null : (PrDTO) redisTemplate.opsForValue().get(prId);
if (Objects.isNull(prDTO)) {
log.info("status=9,proDTO is null;status=9,proDTO is null;");
log.info("status=9,proDTO is null;status=9,proDTO is null;");
log.info("status=9,proDTO is null;status=9,proDTO is null;");
log.info("status=9,proDTO is null;status=9,proDTO is null;");
log.info("status=9,proDTO is null;status=9,proDTO is null;");
}
deviceInfo = deviceMap.get(muid);
replyHeartbeat(deviceInfo, packet, prDTO, tn);
}
/*if ("4".equals(status) && deviceInfo.getStartCount() && device.getPaymentMethod() != null && device.getPaymentMethod() == 2){
log.info("设备状态为4,关机,并且startCount为true");
int count = device.getSurplus() - 1;
device.setSurplus(count);
device.setExpireTime(String.valueOf(count));
deviceMapper.updateById(device);
deviceMap.put(muid, new DeviceInfo(muid,status, deviceIp, devicePort));
}*/
if ("4".equals(status)) {
try {
Device selectById = deviceMapper.selectById(device.getDeviceId());
if (selectById.getForcePay() == 2) {
Device dce = new Device();
dce.setDeviceId(device.getDeviceId());
dce.setForcePay(1);
dce.setStatus("0");
deviceMapper.updateById(dce);
}
if (selectById.getFreeCount() > 0) {
Device device5 = new Device();
device5.setDeviceId(device.getDeviceId());
device5.setFreeCount(selectById.getFreeCount() - 1);
deviceMapper.updateById(device5);
}
changeDeviceOperatingStatus(device.getDeviceId(), 1);
updateRecord(device.getDeviceId());
deleteUserTreatParam(device.getDeviceId());
} catch (Exception e) {
log.error("更新设备使用记录发生异常", e);
}
}
}
}
//马上回
/*log.info("马上回------");
// udpClientService.sendCommand(deviceIp,devicePort,"$01|MU000B57020457D4B100001F97|DT20251011163826|ON0|CF1|WM1|TC000|*7E\r\n");
InetAddress address = null;
try {
address = InetAddress.getByName(deviceIp);
} catch (UnknownHostException e) {
e.printStackTrace();
}
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF1|WM1|TC000|*{CRC}\r\n";
String c02 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
String command = "$01|MU000B57020457D4B100001F97|DT20251011163826|ON0|CF1|WM1|TC000|*7E\r\n";
if ("1".equals(status)){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", "0");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
command = replace.replace("{CRC}", asciiHex);
}
if ("2".equals(status)){
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c02.replace("{uid}", muid)
.replace("{date}", now).replace("{1}", "0").replace("{2}", "000");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
command = replace.replace("{CRC}", asciiHex);
}
byte[] sendData = command.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}*/
}
private void getPR(DeviceInfo info, DatagramPacket packet) {
if (Objects.isNull(info)) {
log.info("method->replyHeartbeat,DeviceInfo is null");
return;
}
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|WM1|TC000|PR1|*{CRC}\r\n";
}
private void replyHeartbeat(DeviceInfo info, DatagramPacket packet, PrDTO prDTO, String tn) {
if (Objects.isNull(info)) {
log.info("method->replyHeartbeat,DeviceInfo is null");
return;
}
String status = info.getStatus();
String muid = info.getMuid();
switch (status) {
case "1":
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|WM1|TC000|*{CRC}\r\n";
Device device = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c01.replace("{uid}", muid).replace("{date}", now)
.replace("{1}", device.getStatus()).replace("{2}", device.getCf());
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
String message = replace.replace("{CRC}", asciiHex);
byte[] sendData = message.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
break;
case "2":
String c02 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
Device device2 = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
String now2 = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace2 = c02.replace("{uid}", muid)
.replace("{date}", now2).replace("{1}", device2.getStatus()).replace("{2}", "000");
String crc2 = replace2.substring(1, replace2.lastIndexOf("*"));
String asciiHex2 = XorUtil.xorAsciiHex(crc2);
String msg = replace2.replace("{CRC}", asciiHex2);
byte[] sendData2 = msg.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket2 = new DatagramPacket(
sendData2,
sendData2.length,
packet.getSocketAddress()
);
try {
serverSocket.send(sendPacket2);
} catch (IOException e) {
e.printStackTrace();
}
break;
case "3":
String defaultDm = "$03|MU{uid}|CI00|CN00|*{CRC}\r\n";
String dm = "$03|MU{uid}|CI{1}|CN{2}|*{CRC}\r\n";
if (prDTO == null) {
log.info("status==3;prDTO=====null;===null");
defaultDm = defaultDm.replace("{uid}", muid);
String substring = defaultDm.substring(1, defaultDm.lastIndexOf("*"));
String xorAsciiHex = XorUtil.xorAsciiHex(substring);
String s = defaultDm.replace("{CRC}", xorAsciiHex);
byte[] sendData3 = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(sendData3, sendData3.length, packet.getSocketAddress());
try {
serverSocket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
}
} else {
log.info("prDTO========={}", prDTO.toString());
redisTemplate.opsForValue().set(prDTO.getId(),prDTO,2,TimeUnit.HOURS);
List<PrDTO.PrItem> prItems = prDTO.getPrItems();
String cn = prItems.size() > 9 ? String.valueOf(prItems.size()) : "0"+prItems.size();
dm = dm.replace("{uid}", muid).replace("{1}", prDTO.getId()).replace("{2}", cn);
String substring = dm.substring(1, dm.lastIndexOf("*"));
String xorAsciiHex = XorUtil.xorAsciiHex(substring);
String s = dm.replace("{CRC}", xorAsciiHex);
log.info("回传给硬件的数据:{}",s);
byte[] sendData3 = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(sendData3, sendData3.length, packet.getSocketAddress());
try {
serverSocket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
}
}
break;
case "9":
if (prDTO == null) {
log.info("case=9,prDTO=null;case=9,prDTO=null;case=9,prDTO=null;case=9,prDTO=null;case=9,prDTO=null;case=9,prDTO=null");
String defaultDm9 = "$03|MU{uid}|CI123456790123456|TN01|FR03|PW50|PL08|UL01|TL017|*{CRC}\r\n";
String substring = defaultDm9.substring(1, defaultDm9.lastIndexOf("*"));
String xorAsciiHex = XorUtil.xorAsciiHex(substring);
String s = defaultDm9.replace("{CRC}", xorAsciiHex);
byte[] sendData3 = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(sendData3, sendData3.length, packet.getSocketAddress());
try {
serverSocket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
}
} else {
List<PrDTO.PrItem> prItems = prDTO.getPrItems();
// String tn1 = tn.substring(1);
PrDTO.PrItem prItem = prItems.get(Integer.parseInt(tn)-1);
String cm9 = "$30|MU{uid}|CI{1}|TN{2}|FR{3}|PW{4}|PL{5}|UL{6}|TL{7}|*{CRC}\r\n";
cm9 = cm9.replace("{uid}", muid).replace("{1}", prDTO.getId()).replace("{2}", tn)
.replace("{3}", prItem.getFr()).replace("{4}", prItem.getPw()).replace("{5}", prItem.getPl())
.replace("{6}", prItem.getUl()).replace("{7}", prItem.getTl());
String substring = cm9.substring(1, cm9.lastIndexOf("*"));
String xorAsciiHex = XorUtil.xorAsciiHex(substring);
String s = cm9.replace("{CRC}", xorAsciiHex);
log.info("status=9,远程下参数数据是:{}",s);
byte[] sendData3 = s.getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(sendData3, sendData3.length, packet.getSocketAddress());
try {
serverSocket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void parseDeviceInfo(Device device, String msg) {
String[] items = msg.split("\\|");
String hw = null, sw = null, ic = null, cf = null, wm = null, cr = null;
for (String item : items) {
if (item.startsWith("HW")) {
hw = item.substring(2);
}
if (item.startsWith("SW")) {
sw = item.substring(2);
}
if (item.startsWith("IC")) {
ic = item.substring(2);
}
if (item.startsWith("CF")) {
cf = item.substring(2);
}
if (item.startsWith("WM")) {
wm = item.substring(2);
}
if (item.startsWith("CR")) {
cr = item.substring(2);
}
}
device.setHw(hw);
device.setSw(sw);
device.setIccid(ic);
if (StringUtils.isBlank(device.getCf())) {
device.setCf(cf);
}
device.setWm(wm);
device.setCr(cr);
device.setOperatingStatus(3);
device.setLastRunTime(LocalDateTime.now());
log.info("解析设备回复的数据得到的硬件版本为:{},软件版本为:{}",hw,sw);
log.info("服务器上的LocalDateTime.now()========={}", LocalDateTime.now());
deviceMapper.updateDeviceInfo(device);
}
private String parseMo(Device device, String msg) {
String[] items = msg.split("\\|");
String mo = null;
for (String item : items) {
if (item.startsWith("MO")) {
mo = item.substring(2);
break;
}
}
device.setMo(mo);
Device dv = new Device();
dv.setDeviceId(device.getDeviceId());
dv.setMo(mo);
deviceMapper.updateById(dv);
return mo;
}
private void deleteUserTreatParam(String deviceId){
LambdaUpdateWrapper<UserTreatmentParameters> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(UserTreatmentParameters::getStatus,0).eq(UserTreatmentParameters::getDeviceId,deviceId);
userTreatmentParametersMapper.update(updateWrapper);
}
private DeviceUsageDetail parseMsgToDetail(String msg) {
String[] items = msg.split("\\|");
String fr = null, pw = null, pl = null, ul = null, wt = null;
for (String item : items) {
if (item.startsWith("FR")) {
fr = item.substring(2);
}
if (item.startsWith("PW")) {
pw = item.substring(2);
}
if (item.startsWith("PL")) {
pl = item.substring(2);
}
if (item.startsWith("UL")) {
ul = item.substring(2);
}
if (item.startsWith("WT")) {
wt = item.substring(2);
}
}
DeviceUsageDetail detail = new DeviceUsageDetail();
detail.setFr(fr);
detail.setPw(pw);
detail.setPl(pl);
detail.setUl(ul);
detail.setWt(wt);
return detail;
}
private void updateLastRunTime(Device device) {
Device d = new Device();
d.setDeviceId(device.getDeviceId());
d.setLastRunTime(LocalDateTime.now());
deviceMapper.updateById(d);
}
private Device toLiliao(String deviceId) {
Device device = deviceMapper.selectById(deviceId);
if (Objects.nonNull(device) && device.getOperatingStatus() != 0) {
device.setOperatingStatus(0);
deviceMapper.updateById(device);
deviceTask.addAbnormalDeviceTask(deviceId);
}
return device;
}
private void recordUse(Device device, DeviceUsageDetail detail) {
if (Objects.isNull(device)) {
log.info("设备使用记录recordUse->device is null");
return;
}
device.setLastRunTime(LocalDateTime.now());
deviceMapper.updateById(device);
DeviceUsageRecord record = deviceUsageRecordMapper.selectOne(new LambdaQueryWrapper<DeviceUsageRecord>()
.eq(DeviceUsageRecord::getDeviceId, device.getDeviceId())
.eq(DeviceUsageRecord::getStartUseFlag, "true"));
if (Objects.nonNull(record)) {
DeviceUsageDetail selectOne = usageDetailMapper.selectOne(new LambdaQueryWrapper<DeviceUsageDetail>().
eq(DeviceUsageDetail::getUsageRecordId, record.getId()).orderByDesc(DeviceUsageDetail::getCreateTime).last("limit 1"));
long seconds = Duration.between(selectOne.getCreateTime(), LocalDateTime.now()).getSeconds();
if ("2".equals(detail.getStatus()) && seconds > 30) {
detail.setUsageRecordId(record.getId());
detail.setCreateTime(LocalDateTime.now());
usageDetailMapper.insert(detail);
}
log.info("设备正在运行");
return;
}
DeviceUsageRecord deviceUsageRecord = new DeviceUsageRecord();
deviceUsageRecord.setStartTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceUsageRecord.setDeviceId(device.getDeviceId());
deviceUsageRecord.setStartUseFlag(UniConstant.CONST_TRUE);
deviceUsageRecord.setWm(device.getWm());
deviceUsageRecord.setBelong(device.getAffiliatedWith());
deviceUsageRecord.setMuid(device.getMuid());
deviceUsageRecordMapper.insert(deviceUsageRecord);
if (device.getPaymentMethod() != null && device.getPaymentMethod() == 2 && device.getSurplus() > 0) {
int count = device.getSurplus() - 1;
device.setSurplus(count);
device.setExpireTime(String.valueOf(count));
deviceMapper.updateById(device);
}
detail.setCreateTime(LocalDateTime.now());
detail.setUsageRecordId(deviceUsageRecord.getId());
/*
如果真要记录待机状态,那么把status=1的recordUse()放开,在这里加上根据deviceUsageRecord.getId()和status=1去查DeviceUsageDetail表,
如果不为空则不插入数据,即只记录一条状态为待机的数据
*/
List<DeviceUsageDetail> usageDetails = usageDetailMapper.selectList(new LambdaQueryWrapper<DeviceUsageDetail>().eq(DeviceUsageDetail::getUsageRecordId, deviceUsageRecord.getId())
.eq(DeviceUsageDetail::getStatus, "1"));
if (CollectionUtils.isEmpty(usageDetails)) {
usageDetailMapper.insert(detail);
}
}
private void updateRecord(String deviceId) {
DeviceUsageRecord deviceUsageRecord = deviceUsageRecordMapper.selectOne(new LambdaQueryWrapper<DeviceUsageRecord>()
.eq(DeviceUsageRecord::getDeviceId, deviceId)
.eq(DeviceUsageRecord::getStartUseFlag, UniConstant.CONST_TRUE));
if (Objects.isNull(deviceUsageRecord)) {
return;
}
deviceUsageRecord.setEndTime(LocalDateTime.now(Clock.systemDefaultZone()));
deviceUsageRecord.setStartUseFlag(UniConstant.CONST_FALSE);
deviceUsageRecordMapper.updateById(deviceUsageRecord);
DeviceUsageDetail detail = new DeviceUsageDetail();
detail.setStatus("4");
detail.setCreateTime(LocalDateTime.now());
detail.setUsageRecordId(deviceUsageRecord.getId());
usageDetailMapper.insert(detail);
}
private void changeDeviceOperatingStatus(String deviceId, Integer status) {
Device device = deviceMapper.selectById(deviceId);
if (Objects.isNull(device)) {
return;
}
device.setOperatingStatus(status);
deviceMapper.updateById(device);
}
public boolean reply(Device device, String status) {
String muid = device.getMuid();
DeviceInfo deviceInfo = getDeviceInfo(muid);
if (deviceInfo == null) {
log.info("设备会话已过期,请设备先上报一次数据");
return false;
}
String message = "";
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|WM1|TC000|*{CRC}\r\n";
String c02 = "$02|MU{uid}|DT{date}|ON{1}|SN{2}|*{CRC}\r\n";
if ("1".equals(deviceInfo.getStatus())) {
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", status).replace("{2}", device.getCf());
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
message = replace.replace("{CRC}", asciiHex);
} else if ("2".equals(deviceInfo.getStatus())) {
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String replace = c02.replace("{uid}", muid)
.replace("{date}", now).replace("{1}", status).replace("{2}", "000");
String crc = replace.substring(1, replace.lastIndexOf("*"));
String asciiHex = XorUtil.xorAsciiHex(crc);
message = replace.replace("{CRC}", asciiHex);
} else if ("4".equals(deviceInfo.getStatus())) {
log.info("设备为关机状态");
return true;
}
try {
log.info("最终发送的数据为:{}", message);
byte[] data = message.getBytes(StandardCharsets.UTF_8);
InetAddress address = InetAddress.getByName(deviceInfo.getIp());
DatagramPacket packet = new DatagramPacket(data, data.length, address, deviceInfo.getPort());
serverSocket.send(packet);
System.out.printf("UDP Reply sent to %s:%d -> %s%n", deviceInfo.getIp(), deviceInfo.getPort(), message);
device.setStatus(status);
deviceMapper.updateById(device);
return true;
} catch (IOException e) {
log.info("回复设备UDP消息发送异常");
e.printStackTrace();
return true;
}
}
/**
* 根据实际协议解析设备ID
* 这是一个示例,您需要根据硬件数据包的实际格式重写此方法
*
* @param message 收到的原始消息
* @return 解析出的设备ID
*/
private String parsemuid(String message) {
Pattern p = Pattern.compile("^[^|]*\\|([^|]+)\\|");
Matcher matcher = p.matcher(message);
if (matcher.find()) {
return matcher.group(1).substring(2);
}
// 如果协议里没有ID,也可以用IP:端口作为唯一标识
return null;
}
/**
* 获取设备信息
*
* @param muid 设备ID
* @return 设备信息,如果不存在则返回null
*/
public DeviceInfo getDeviceInfo(String muid) {
DeviceInfo deviceInfo = deviceMap.get(muid);
if (Objects.nonNull(deviceInfo) && System.currentTimeMillis() - deviceInfo.getLastActiveTime() < SESSION_TIMEOUT) {
return deviceInfo;
} else {
if (Objects.isNull(deviceInfo)) {
deviceMap.remove(muid);
return null;
}
if (!deviceInfo.getStartCount()) {
deviceMap.remove(muid);
}
return null;
}
}
/**
* 获取所有在线设备
*/
public ConcurrentHashMap<String, DeviceInfo> getOnlineDevices() {
return new ConcurrentHashMap<>(deviceMap);
}
private void replyUpgradeHeartbeat(DeviceInfo info, DatagramPacket packet) {
if (Objects.isNull(info)) {
log.info("method->replyHeartbeat,DeviceInfo is null");
return;
}
String c01 = "$01|MU{uid}|DT{date}|ON{1}|CF{2}|UP{3}|WM1|TC000|*{CRC}\r\n";
/*
查device_upgrade表拿到当前版本升级包名称,然后去/opt/jkadmin/upgrade下面拿到升级包
调fileService拆包,得到包数量,替换{3},然后回复
*/
DeviceUpgrade deviceUpgrade = upgradeMapper.selectOne(new LambdaQueryWrapper<DeviceUpgrade>().eq(DeviceUpgrade::getBeCurrentVersion, 1));
if (Objects.isNull(deviceUpgrade)) {
replyHeartbeat(info, packet, null, null);
} else {
String fileUrl = UPGRADE_PACKAGE_ROOT_PATH + deviceUpgrade.getFileName();
List<byte[]> packages = null;
try {
packages = fileService.splitFileWithNIO(fileUrl);
} catch (IOException e) {
log.error("拆包发生异常-.-.-.-.-.-.-.-.-.", e);
}
if (CollectionUtils.isNotEmpty(packages) && packages.size() <= 32) {
String muid = info.getMuid();
Device device = deviceMapper.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getMuid, muid));
if (Objects.isNull(device)) {
return;
}
String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String r = c01.replace("{uid}", muid).replace("{date}", now).replace("{1}", device.getStatus())
.replace("{2}", device.getCf()).replace("{3}", String.valueOf(packages.size()));
String crc = r.substring(1, r.lastIndexOf("*"));
String xorAsciiHex = XorUtil.xorAsciiHex(crc);
String msg = r.replace("{CRC}", xorAsciiHex);
log.info("回复升级心跳数据:{}", msg);
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, packet.getSocketAddress());
try {
serverSocket.send(sendPacket);
} catch (IOException e) {
log.error("回复升级心跳包发生异常", e);
}
} else {
log.info("升级包为空或升级包大小超过1024*32");
replyHeartbeat(info, packet, null, null);
}
}
}
}
@@ -0,0 +1,34 @@
package cn.unizk.service;
import cn.unizk.properties.SysWxProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* @Author hao
* @Date 2025/9/11 10:04
* @Version 1.0
**/
@Service
@Slf4j
public class WxAuthLoginService {
@Resource
private SysWxProperties wxProperties;
private static String authUrl = "https://open.weixin.qq.com/connect/qrconnect?appid=${1}&redirect_uri=${2}&response_type=code&scope=snsapi_login&state=${3}#wechat_redirect";
public void auth(HttpServletResponse response,String deviceId) throws IOException {
log.info("deviceId={}",deviceId);
String redirectUrl = URLEncoder.encode(wxProperties.getRedirectUrl(), StandardCharsets.UTF_8.name());
authUrl = authUrl.replace("${1}",wxProperties.getAppid()).replace("${2}",redirectUrl)
.replace("${3}",deviceId);
response.sendRedirect(authUrl);
}
}
@@ -0,0 +1,9 @@
/*
package cn.unizk.service;
import cn.unizk.entity.po.WxUser;
import com.baomidou.mybatisplus.extension.service.IService;
public interface WxUserService extends IService<WxUser> {
}
*/
Loaded 100 of 1456 files, more files were not shown because too many files have changed in this diff. Show more