@ -0,0 +1,35 @@ |
|||
HELP.md |
|||
target/ |
|||
!.mvn/wrapper/maven-wrapper.jar |
|||
!**/src/main/**/target/ |
|||
!**/src/test/**/target/ |
|||
|
|||
### STS ### |
|||
.apt_generated |
|||
.classpath |
|||
.factorypath |
|||
.project |
|||
.settings |
|||
.springBeans |
|||
.sts4-cache |
|||
|
|||
### IntelliJ IDEA ### |
|||
.idea |
|||
*.iws |
|||
*.iml |
|||
*.ipr |
|||
|
|||
### NetBeans ### |
|||
/nbproject/private/ |
|||
/nbbuild/ |
|||
/dist/ |
|||
/nbdist/ |
|||
/.nb-gradle/ |
|||
build/ |
|||
!**/src/main/**/build/ |
|||
!**/src/test/**/build/ |
|||
|
|||
### VS Code ### |
|||
.vscode/ |
|||
/output/ |
|||
/file-online-preview/server/src/main/file |
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2023 风行 |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,144 @@ |
|||
### 系统简介 |
|||
|
|||
企业级通用开发平台,前后端分离架构,单工程,多模块,部署形态为单体应用。 |
|||
前端基于vue3.2.47,element-plus 2.1.0,前端框架vue-element-plus-admin1.9.4深度整合改造。 |
|||
后端SSM+MybatisPlus,使用SpringBoot 2.3.0。 |
|||
数据库使用MySql 8.0. |
|||
|
|||
重度使用MybatisPlus,包括主键策略、逻辑删除、乐观锁、自动填充、数据分页、CURD接口、条件构造器等, |
|||
二次封装和扩展代码生成器,实现entity、dao、service、controller、vo及前端vue页面生成。 |
|||
|
|||
整体架构图如下: |
|||
 |
|||
|
|||
技术选型,详见专栏博客:https://blog.csdn.net/seawaving/article/details/130015830 |
|||
|
|||
### 后端架构 |
|||
|
|||
到目前为止,整个工程项目,后端共计19个模块,架构图和依赖关系如下图所示: |
|||
 |
|||
|
|||
模块分成三类,一类是平台内核模块,命名规则是platform+模块功能名称,图中用蓝色标示;一类是能力扩展模块,命名规则是platform-boot-starter+模块功能名称,图中用绿色标示;剩下的一类是接口平台,命名规则是cip |
|||
+模块功能名称,图中用紫色标示,相对平台独立,但又作为平台的重要组成部分。 |
|||
|
|||
#### 平台内核模块 |
|||
|
|||
platform-common作为公用基础模块,主要包括工具类、公用注解、公共父类、公共常量、公共枚举值,与前端UI交互定义的vo类,该模块为最基础的模块,无前置依赖。 |
|||
|
|||
platform-system是平台最核心的模块,主要包括组织机构、人员、角色(用户组)、权限、日志、系统参数、模块这些实体和服务的实现,需要注意的是,权限控制、日志记录,并不是在该模块实现,而是在platform-framework平台框架中实现,该模块依赖于platform-common。 |
|||
|
|||
platform-framework是平台框架,负责身份认证、权限控制、全局配置、数据分页、日志处理、自动填充(创建人、创建时间、修改人、修改时间),因为身份认证、权限控制等功能,不可避免需要使用处于platform-system模块中的人员、用户组等实体和服务,因此依赖于platform-system。 |
|||
|
|||
platform-support是一个业务支撑模块,基于技术组件进行功能设计与封装,实现一些通用的功能设计,更方便业务逻辑的实现,提供附件管理、通知公告、内容模板(用于短信、邮件、消息)、单据流水号、门户等功能。这些支撑模块同样需要位于platform-system模块中的人员、组织机构等实体和服务,因此依赖于platform-system。 |
|||
|
|||
platform-entityconfig属于低代码配置范畴,定义了业务实体的元数据,通过模块、实体、模型、视图多级配置,结合模板技术,实现细粒度的代码生成控制。 |
|||
|
|||
platform-workflow集成了工作流组件activiti的分支Camunda,实现了流程建模、流程模板,以及我的待办、我的已办和我的申请等功能。 |
|||
|
|||
platform-businessflow是业务流程的集中存储模块,实现了流程导航功能和请求申请示例流程。 |
|||
|
|||
platform-boot-starter是平台启动项目,整合平台基础功能,类似于spring-boot-starter,业务系统引入该包进行依赖。该模块自身没有实体与服务,而是汇总整合,把platform-framework |
|||
引用进来,同时进行配置。配置分两方面,一方面是做一个配置类,加一些注解(如:@EnableRetry、@ServletComponentScan、@EnableTransactionManagement),使用开发平台实现的业务系统,就不需要在启动类上重复添加这些注解;另一方面,是位于yml配置文件中的配置信息,也分为两部分,一部分是三方组件自身的,如数据源、连接池、redis、quartz、logback,另一方面是自定义的系统参数,如用户默认密码、导出excel数据的批次最大行数量。 |
|||
|
|||
platform-boot-starter-demo是示例项目,实际是模拟业务系统如何使用开发平台,用于平台自身功能开发与调试。 |
|||
|
|||
#### 能力扩展模块 |
|||
|
|||
绿色标示的五个模块,比较好理解,通常是对第三方组件的封装与整合,依赖于公共基础模块platform-common,这些模块可以不断扩展的,业务系统按需引入即可,这样就实现了核心模块必选、扩展模块可选的目的。 |
|||
|
|||
* platform-boot-starter-mail:邮件,集成springmail组件,实现邮件的发送功能封装 |
|||
* platform-boot-starter-oss: 对象存储,用于文件存储封装,底层可基于多种模式,如本地磁盘、对象存储系统等 |
|||
* platform-boot-starter-scheduler:任务调度,集成quartz组件,实现任务调度可视化配置 |
|||
* platform-boot-starter-notification:消息通知,基于netty实现的websocket,用于系统内置消息 |
|||
* platform-boot-starter-elasticsearch:全文搜索,集成elasticsearch组件 |
|||
对于扩展模块,平台的核心模块实际也可能会用到,例如platform-support中的附件功能,就会用到platform-boot-starter-oss;platform-system中的自动解锁用户功能,就会用到platform-boot-starter-scheduler。 |
|||
|
|||
#### 接口平台 |
|||
|
|||
将自己之前开源的通用接口平台进行了改造,将其作为一个模块,整合到应用开发平台当中来,由接口平台统一对外暴露应用系统的API数据接口以及推送事件消息。 |
|||
platform-cip-common:公共基础 |
|||
platform-cip-api:对外提供API数据接口,提供API服务 |
|||
platform-cip-message:基于netty的web socket服务端提供消息服务 |
|||
platform-cip-manage:平台自身基础数据的维护,如应用、API服务、消息服务、数据权限管理等。 |
|||
4个模块内关系为manage依赖common,api和message相互独立,但都依赖于manage。 |
|||
|
|||
### 如何运行 |
|||
|
|||
以下为简要说明,详细的开发环境搭建手册参见https://blog.csdn.net/seawaving/article/details/134895546 |
|||
|
|||
#### 1. 准备工作 |
|||
|
|||
预装redis、nodejs、mysql、ide |
|||
|
|||
#### 2. 初始化数据库 |
|||
|
|||
执行/resource目录下的init.sql,创建名字为abc的数据库。 |
|||
|
|||
#### 3 .前端 |
|||
|
|||
nodejs >=14.6 |
|||
|
|||
执行npm install pnpm -g,安装pnpm包 |
|||
|
|||
执行pnpm install命令,若nodejs版本过低会提示 |
|||
|
|||
使用vscode打开platform-web目录,执行pnpm install安装npm module |
|||
|
|||
执行结束会提示如下错误,不用理会,因为把husky移除导致的,不影响系统正常运行,进行下步dev脚本即可 |
|||
husky install |
|||
'husky' 不是内部或外部命令,也不是可运行的程序 |
|||
或批处理文件。 |
|||
|
|||
执行dev脚本,默认打开localhost:4000 |
|||
|
|||
默认管理员账号密码:admin/12345678 |
|||
|
|||
#### 4 .后端 |
|||
|
|||
标准SpringBoot项目,多模块,启动类位于platform-boot-starter-demo下,默认端口8080。 |
|||
|
|||
注:系统的下拉数据源,也即数据字典使用redis缓存,按上述步骤构建后,部分查询界面不显示中文名称,可在系统登录后,访问系统管理-》系统维护菜单下的“重建缓存”按钮,系统会自动将数据库的字典数据写入到redis中。 |
|||
|
|||
#### 5 .接口平台对接客户端 |
|||
|
|||
cip-client是一个模拟的接口平台客户端,是一个独立的springboot,相当于第三方系统,有自己独立的数据库,数据库脚本参见\cip-client\src\main\resources\init.sql |
|||
|
|||
#### 6 .minio启用说明 |
|||
|
|||
平台对于文件存储除了支持本地磁盘模式外,还实现了minio对象存储组件的集成。如需启用,需安装minio服务端,版本2021-04-22T15-44-28Z(最后一个基于apache |
|||
2.0开源协议的版本),并修改平台配置文件。 |
|||
|
|||
### 未来规划 |
|||
|
|||
客观地说,目前开发平台已经实现了大部分常用常见功能,可以投入使用了。 |
|||
由于是一路狂奔模式,速度提升,时间缩短,但不可避免一些功能遗留了待办项,以及未充分测试导致存在bug,后面需要再循着功能过一遍,进行重构、测试,完善功能,输出设计。特别是低代码配置部分,需要持续完善与改进,简化配置,进一步提升开发效率。 |
|||
|
|||
后面几块是平台欠缺的,需要补全和完善,每一块都是硬骨头,难度和工作量都不小,做了一些简单初步的了解,具体如下: |
|||
|
|||
输出系统使用手册(进行中) |
|||
集成图表组件(已完成) |
|||
集成工作流(常用功能已完成,进度80%,已可用) |
|||
实现可视化表单(常用功能已完成,进度90%,待打磨) |
|||
移动端实现(整合了一个移动端框架,打通了认证,尚未有具体的功能,暂挂起) |
|||
自定义查询(未开始) |
|||
实现数据权限(未开始) |
|||
|
|||
### 基于平台开发的应用 |
|||
|
|||
#### 文档管理系统 |
|||
|
|||
文档管理系统是基于平台实现的文档库管理应用,可作为企业内部文档库、知识库使用,主要包括以下功能: |
|||
|
|||
* 文件夹管理:创建、更名、删除、复制、移动、授权; |
|||
* 文档管理:上传、下载、更名、复制、移动、预览、分享; |
|||
* 权限控制:按组织机构和按用户组两种授权模式; |
|||
* 在线预览:无需下载,上百种格式文件在线预览; |
|||
* 收藏夹:支持将文件夹和文档加入收藏、查看和移除; |
|||
* 全文搜索:对文本类、office文档和pdf文档等进行全文搜索; |
|||
|
|||
### 系统设计资料 |
|||
|
|||
参见csdn博客专栏 [https://blog.csdn.net/seawaving/category_12230971.html](https://blog.csdn.net/seawaving/category_12230971.html) |
|||
平台研发过程中的设计思路、遇到的问题和方案的选择等一并分享出来,欢迎交流与讨论。 |
|||
|
|||
|
@ -0,0 +1,110 @@ |
|||
<?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"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<groupId>tech.popsoft.cip</groupId> |
|||
<artifactId>cip-client</artifactId> |
|||
<version>5.1.0</version> |
|||
|
|||
<name>cip-client</name> |
|||
|
|||
|
|||
<properties> |
|||
<!-- 文件拷贝时的编码 --> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
|||
<!-- 编译时的编码 --> |
|||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> |
|||
<maven.compiler.source>1.8</maven.compiler.source> |
|||
<maven.compiler.target>1.8</maven.compiler.target> |
|||
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> |
|||
<java.version>1.8</java.version> |
|||
</properties> |
|||
|
|||
|
|||
<!--版本依赖管理--> |
|||
<dependencyManagement> |
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-dependencies</artifactId> |
|||
<version>2.3.0.RELEASE</version> |
|||
<type>pom</type> |
|||
<scope>import</scope> |
|||
</dependency> |
|||
|
|||
</dependencies> |
|||
</dependencyManagement> |
|||
|
|||
<dependencies> |
|||
|
|||
<!--######开发平台核心模块依赖 区域开始#####--> |
|||
<dependency> |
|||
<groupId>tech.abc</groupId> |
|||
<artifactId>platform-framework</artifactId> |
|||
<version>5.1.0</version> |
|||
</dependency> |
|||
<!--######开发平台核心模块依赖 区域开始#####--> |
|||
|
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-web</artifactId> |
|||
|
|||
</dependency> |
|||
|
|||
|
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-test</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
|
|||
|
|||
<dependency> |
|||
<groupId>tech.abc</groupId> |
|||
<artifactId>platform-cip-common</artifactId> |
|||
<version>5.1.0</version> |
|||
|
|||
</dependency> |
|||
|
|||
<!--mysql 驱动--> |
|||
<dependency> |
|||
<groupId>mysql</groupId> |
|||
<artifactId>mysql-connector-java</artifactId> |
|||
<version>8.0.20</version> |
|||
</dependency> |
|||
|
|||
<!--数据库连接池--> |
|||
<dependency> |
|||
<groupId>com.alibaba</groupId> |
|||
<artifactId>druid-spring-boot-starter</artifactId> |
|||
<version>1.2.5</version> |
|||
</dependency> |
|||
|
|||
|
|||
</dependencies> |
|||
|
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-compiler-plugin</artifactId> |
|||
<version>3.8.1</version> |
|||
<configuration> |
|||
<source>1.8</source> |
|||
<target>1.8</target> |
|||
<encoding>UTF-8</encoding> |
|||
</configuration> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</project> |
@ -0,0 +1,73 @@ |
|||
package tech.popsoft.cip.client; |
|||
|
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.mybatis.spring.annotation.MapperScan; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.CommandLineRunner; |
|||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|||
import org.springframework.boot.autoconfigure.web.ServerProperties; |
|||
import org.springframework.boot.builder.SpringApplicationBuilder; |
|||
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
|||
import org.springframework.boot.web.servlet.ServletComponentScan; |
|||
import org.springframework.context.ConfigurableApplicationContext; |
|||
import org.springframework.context.annotation.Import; |
|||
import org.springframework.retry.annotation.EnableRetry; |
|||
import org.springframework.transaction.annotation.EnableTransactionManagement; |
|||
import org.springframework.util.StopWatch; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.abc.platform.framework.config.PlatformConfig; |
|||
import tech.popsoft.cip.client.framework.MessageClient; |
|||
import tech.popsoft.cip.client.framework.MessageClientConfig; |
|||
|
|||
|
|||
/** |
|||
* @author wqliu |
|||
*/ |
|||
@Slf4j |
|||
@Import(SpringUtil.class) |
|||
@EnableRetry |
|||
@ServletComponentScan |
|||
@EnableTransactionManagement |
|||
@EnableConfigurationProperties({MessageClientConfig.class, PlatformConfig.class}) |
|||
@SpringBootApplication(scanBasePackages = {"tech.abc.platform.**", "tech.popsoft.cip.**"}) |
|||
@MapperScan({"tech.abc.platform.**.mapper", "tech.popsoft.cip.**.mapper"}) |
|||
public class CipClientApplication implements CommandLineRunner { |
|||
|
|||
@Autowired |
|||
private MessageClient messageClient; |
|||
|
|||
public static void main(String[] args) { |
|||
|
|||
|
|||
StopWatch stopWatch = new StopWatch(); |
|||
stopWatch.start(); |
|||
ConfigurableApplicationContext context = new SpringApplicationBuilder(CipClientApplication.class) |
|||
.logStartupInfo(false) |
|||
.run(args); |
|||
stopWatch.stop(); |
|||
Integer port = context.getBean(ServerProperties.class).getPort(); |
|||
log.info("服务启动完成,耗时:{}s,端口号:{}", stopWatch.getTotalTimeSeconds(), port); |
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void run(String... args) throws Exception { |
|||
|
|||
// 此处通过单线程启动netty,是为了不堵塞主应用,不需要线程池
|
|||
// noinspection AlibabaAvoidManuallyCreateThread
|
|||
Thread thread = new Thread(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
messageClient.start(); |
|||
} |
|||
}); |
|||
// 启动netty服务
|
|||
thread.start(); |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,112 @@ |
|||
package tech.popsoft.cip.client.framework; |
|||
|
|||
import io.netty.bootstrap.Bootstrap; |
|||
import io.netty.channel.Channel; |
|||
import io.netty.channel.ChannelFuture; |
|||
import io.netty.channel.ChannelFutureListener; |
|||
import io.netty.channel.EventLoopGroup; |
|||
import io.netty.channel.nio.NioEventLoopGroup; |
|||
import io.netty.channel.socket.nio.NioSocketChannel; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
import tech.popsoft.cip.client.framework.customhandler.WebSocketClientHandshakerHandler; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* @author wqliu |
|||
* @date 2021-9-28 |
|||
**/ |
|||
@Slf4j |
|||
@Component |
|||
public class MessageClient { |
|||
|
|||
|
|||
@Autowired |
|||
private MessageClientConfig config; |
|||
|
|||
@Autowired |
|||
private MessageClientChannelInitializer messageClientChannelInitializer; |
|||
|
|||
@Autowired |
|||
private ResendMessage resendMessage; |
|||
|
|||
|
|||
/** |
|||
* 启动客户端方法 |
|||
*/ |
|||
public void start() { |
|||
|
|||
EventLoopGroup workerGroup = new NioEventLoopGroup(1); |
|||
try { |
|||
Bootstrap bootstrap = new Bootstrap(); |
|||
bootstrap.group(workerGroup); |
|||
bootstrap.channel(NioSocketChannel.class); |
|||
bootstrap.handler(messageClientChannelInitializer); |
|||
|
|||
// 客户端与服务端连接的通道,final修饰表示只会有一个
|
|||
ChannelFuture channelFuture = bootstrap.connect(config.getHost(), config.getPort()); |
|||
|
|||
channelFuture.addListener(new ChannelFutureListener() { |
|||
@Override |
|||
public void operationComplete(ChannelFuture future) throws Exception { |
|||
if (!future.isSuccess()) { |
|||
// 未成功
|
|||
log.error("连接失败", future.cause()); |
|||
// 执行重连
|
|||
reconnect(); |
|||
} else { |
|||
log.info("连接成功"); |
|||
Channel channel = future.channel(); |
|||
// 将channel保存到全局变量
|
|||
MessageClientGlobalHolder.channel = channel; |
|||
// 发起握手
|
|||
WebSocketClientHandshakerHandler handler = (WebSocketClientHandshakerHandler) channel.pipeline().get("hookedHandler"); |
|||
handler.handshake(MessageClientGlobalHolder.channel); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// 消息重发
|
|||
if (config.isEnableResend()) { |
|||
log.info("启动消息重发机制"); |
|||
// 延迟30秒启动,给建立连接预留充足的时间
|
|||
workerGroup.scheduleAtFixedRate(() -> { |
|||
resendMessage.resend(); |
|||
}, 30, config.getSendMessageSpan(), TimeUnit.SECONDS); |
|||
} |
|||
// 等待服务器端关闭
|
|||
channelFuture.channel().closeFuture().sync(); |
|||
|
|||
|
|||
} catch (Exception e) { |
|||
log.error("消息客户端启动失败:{}" + e.getMessage(), e); |
|||
// 执行重连
|
|||
reconnect(); |
|||
} finally { |
|||
workerGroup.shutdownGracefully(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 重连 |
|||
*/ |
|||
public void reconnect() { |
|||
try { |
|||
// 重连期间将连接置空
|
|||
MessageClientGlobalHolder.channel = null; |
|||
// 休眠5秒
|
|||
Thread.sleep(5000); |
|||
// 执行重连
|
|||
log.info("消息客户端进行重连"); |
|||
start(); |
|||
|
|||
} catch (InterruptedException e) { |
|||
log.error("消息客户端重连过程中线程休眠失败", e); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,142 @@ |
|||
package tech.popsoft.cip.client.framework; |
|||
|
|||
|
|||
import io.netty.channel.ChannelInitializer; |
|||
import io.netty.channel.ChannelPipeline; |
|||
import io.netty.channel.socket.SocketChannel; |
|||
import io.netty.handler.codec.http.HttpClientCodec; |
|||
import io.netty.handler.codec.http.HttpObjectAggregator; |
|||
import io.netty.handler.ssl.SslHandler; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.core.env.Environment; |
|||
import org.springframework.core.io.ClassPathResource; |
|||
import org.springframework.stereotype.Component; |
|||
import tech.abc.platform.cip.common.handler.JsonEncodeHandler; |
|||
import tech.abc.platform.cip.common.handler.MessageTypeDecodeHandler; |
|||
import tech.abc.platform.cip.common.handler.TextWebSocketFrameEncodeHandler; |
|||
import tech.abc.platform.cip.common.handler.ValidateMessageHandler; |
|||
import tech.popsoft.cip.client.framework.customhandler.*; |
|||
|
|||
import javax.net.ssl.KeyManagerFactory; |
|||
import javax.net.ssl.SSLContext; |
|||
import javax.net.ssl.SSLEngine; |
|||
import java.io.InputStream; |
|||
import java.security.KeyStore; |
|||
|
|||
/** |
|||
* 初始化通道 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-2-5 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class MessageClientChannelInitializer extends ChannelInitializer<SocketChannel> { |
|||
|
|||
|
|||
@Autowired |
|||
private MessageClientConfig config; |
|||
|
|||
@Autowired |
|||
private Environment environment; |
|||
|
|||
/** |
|||
* 生产运行模式 |
|||
*/ |
|||
private final String PRD_MODE = "prd"; |
|||
|
|||
/** |
|||
* 初始化channel |
|||
*/ |
|||
@Override |
|||
public void initChannel(SocketChannel socketChannel) throws Exception { |
|||
|
|||
|
|||
// 获取通道链路
|
|||
ChannelPipeline pipeline = socketChannel.pipeline(); |
|||
|
|||
// 仅在生产模式下加载ssl过滤器
|
|||
String mode = environment.getProperty("spring.profiles.active"); |
|||
if (PRD_MODE.equals(mode)) { |
|||
// ssl
|
|||
SSLContext sslContext = createSslContext(); |
|||
SSLEngine engine = sslContext.createSSLEngine(); |
|||
engine.setNeedClientAuth(false); |
|||
engine.setUseClientMode(false); |
|||
// 客户端第一个消息,即发起握手的消息不要加密
|
|||
pipeline.addLast(new SslHandler(engine, true)); |
|||
} |
|||
|
|||
|
|||
// HTTP 编解码
|
|||
pipeline.addLast(new HttpClientCodec()); |
|||
|
|||
// 聚合为单个 FullHttpRequest 或者 FullHttpResponse
|
|||
pipeline.addLast(new HttpObjectAggregator(64 * 1024)); |
|||
|
|||
|
|||
// 读超时处理
|
|||
pipeline.addLast(new CustomReadTimeoutHandler(config.getReadIdleTimeOut())); |
|||
|
|||
|
|||
// 处理web socket协议与握手
|
|||
pipeline.addLast("hookedHandler", new WebSocketClientHandshakerHandler()); |
|||
|
|||
|
|||
// 数据基本验证
|
|||
pipeline.addLast(new ValidateMessageHandler()); |
|||
|
|||
// 去重
|
|||
pipeline.addLast(new DistinctMessageHandler()); |
|||
|
|||
|
|||
// 将文本按消息类型转换为请求消息或响应消息
|
|||
pipeline.addLast(new MessageTypeDecodeHandler()); |
|||
|
|||
// 请求消息业务逻辑处理器
|
|||
pipeline.addLast(new RequestMessageBusinessHandler()); |
|||
|
|||
|
|||
// 响应消息业务逻辑处理器
|
|||
pipeline.addLast(new ResponseMessageBusinessHandler()); |
|||
|
|||
// 编码为TextWebSocketFrame
|
|||
pipeline.addLast(new TextWebSocketFrameEncodeHandler()); |
|||
|
|||
// json序列化
|
|||
pipeline.addLast(new JsonEncodeHandler()); |
|||
|
|||
|
|||
} |
|||
|
|||
/** |
|||
* 创建ssl上下文对象 |
|||
* |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public SSLContext createSslContext() throws Exception { |
|||
|
|||
// 读取配置信息
|
|||
String path = environment.getProperty("server.ssl.key-store"); |
|||
String password = environment.getProperty("server.ssl.key-store-password"); |
|||
String type = environment.getProperty("server.ssl.key-store-type"); |
|||
|
|||
// 构建证书上下文对象
|
|||
KeyStore ks = KeyStore.getInstance(type); |
|||
path = path.replace("classpath:", ""); |
|||
ClassPathResource resource = new ClassPathResource(path); |
|||
InputStream ksInputStream = resource.getInputStream(); |
|||
ks.load(ksInputStream, password.toCharArray()); |
|||
// KeyManagerFactory充当基于密钥内容源的密钥管理器的工厂。
|
|||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); |
|||
kmf.init(ks, password.toCharArray()); |
|||
// SSLContext的实例表示安全套接字协议的实现,它充当用于安全套接字工厂或 SSLEngine 的工厂。
|
|||
SSLContext sslContext = SSLContext.getInstance("TLS"); |
|||
sslContext.init(kmf.getKeyManagers(), null, null); |
|||
return sslContext; |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,88 @@ |
|||
package tech.popsoft.cip.client.framework; |
|||
|
|||
|
|||
import lombok.Data; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
|
|||
import java.text.MessageFormat; |
|||
|
|||
|
|||
/** |
|||
* 客户端配置参数 |
|||
* 使用spring单例模式 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-5 |
|||
*/ |
|||
@Data |
|||
@ConfigurationProperties(prefix = "params") |
|||
public class MessageClientConfig { |
|||
|
|||
|
|||
/** |
|||
* 应用编码 |
|||
*/ |
|||
private String appCode; |
|||
/** |
|||
* 应用密钥 |
|||
*/ |
|||
private String appSecret; |
|||
/** |
|||
* 消息中心主机 |
|||
*/ |
|||
private String host; |
|||
/** |
|||
* 消息中心端口 |
|||
*/ |
|||
private int port = 8997; |
|||
|
|||
/** |
|||
* 心跳频率,单位秒 |
|||
*/ |
|||
private int heartbeatRate = 5; |
|||
|
|||
|
|||
/** |
|||
* 最大发送次数 |
|||
*/ |
|||
private int maxSendCount = 4; |
|||
|
|||
/** |
|||
* 定时发送消息间隔时间,单位秒 |
|||
*/ |
|||
private int sendMessageSpan = 30; |
|||
|
|||
/** |
|||
* 每次定时发送消息的数量 |
|||
*/ |
|||
private int sendMessageCount = 10; |
|||
|
|||
|
|||
/** |
|||
* 是否启用消息重发 |
|||
*/ |
|||
private boolean enableResend; |
|||
|
|||
/** |
|||
* web socket的路径 |
|||
*/ |
|||
private String webSocketPath; |
|||
|
|||
|
|||
/** |
|||
* 触发读空闲事件的时间,单位秒 |
|||
*/ |
|||
private int readIdleTimeOut; |
|||
|
|||
/** |
|||
* 获取web socket的url地址 |
|||
*/ |
|||
public String getWebSocketUrl() { |
|||
String url = MessageFormat.format("ws://{0}:{1}/{2}", this.getHost(), String.valueOf(this.getPort()), |
|||
this.getWebSocketPath()); |
|||
return url; |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,18 @@ |
|||
package tech.popsoft.cip.client.framework; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import lombok.experimental.UtilityClass; |
|||
|
|||
/** |
|||
* 客户端全局容器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-2-3 9:28 |
|||
**/ |
|||
@UtilityClass |
|||
public class MessageClientGlobalHolder { |
|||
/** |
|||
* 通道 |
|||
*/ |
|||
public static Channel channel = null; |
|||
} |
@ -0,0 +1,49 @@ |
|||
package tech.popsoft.cip.client.framework; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
import tech.popsoft.cip.client.framework.sender.MessageSenderFactory; |
|||
import tech.popsoft.cip.client.framework.sender.RequestMessageSender; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 重发消息 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-24 |
|||
**/ |
|||
@Component |
|||
@Slf4j |
|||
public class ResendMessage { |
|||
@Autowired |
|||
private ApiMessageLogService apiMessageLogService; |
|||
|
|||
@Autowired |
|||
private MessageClientConfig config; |
|||
|
|||
public void resend() { |
|||
// 需要进行异常处理,否则某次异常会导致定时器停止运行
|
|||
try { |
|||
// 判断连接状态
|
|||
if (MessageClientGlobalHolder.channel != null) { |
|||
// 查找待重发的消息
|
|||
List<ApiMessageLog> list = |
|||
apiMessageLogService.getResendMessage(config.getSendMessageCount(), config.getMaxSendCount()); |
|||
for (int i = 0; i < list.size(); i++) { |
|||
ApiMessageLog log = list.get(i); |
|||
// 根据消息主题构建发送器
|
|||
RequestMessageSender sender = (RequestMessageSender) MessageSenderFactory.createSender(log.getRequestTopicCode()); |
|||
// 传入原请求的消息标识和消息内容
|
|||
sender.sendMessage(log.getRequestData(), log.getRequestId()); |
|||
} |
|||
|
|||
} |
|||
} catch (Exception e) { |
|||
log.error("消息重发处理异常", e); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
package tech.popsoft.cip.client.framework.customhandler; |
|||
|
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.timeout.ReadTimeoutHandler; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.MessageClient; |
|||
import tech.popsoft.cip.client.framework.MessageClientGlobalHolder; |
|||
|
|||
|
|||
/** |
|||
* 自定义的读超时处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-2-10 |
|||
**/ |
|||
@Slf4j |
|||
public class CustomReadTimeoutHandler extends ReadTimeoutHandler { |
|||
public CustomReadTimeoutHandler(int timeoutSeconds) { |
|||
super(timeoutSeconds); |
|||
} |
|||
|
|||
@Override |
|||
protected void readTimedOut(ChannelHandlerContext ctx) throws Exception { |
|||
log.info("读空闲"); |
|||
// 关闭连接
|
|||
ctx.channel().close(); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
|||
// 置空连接通道
|
|||
MessageClientGlobalHolder.channel = null; |
|||
log.info("客户端检测通道失效,启动重连"); |
|||
MessageClient messageClient = SpringUtil.getBean(MessageClient.class); |
|||
messageClient.reconnect(); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
|||
log.info("与服务端建立连接,通道开启!"); |
|||
// 必须调用父类方法,避免其他处理器的channelActive事件不再触发
|
|||
super.channelActive(ctx); |
|||
|
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
package tech.popsoft.cip.client.framework.customhandler; |
|||
|
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.channel.SimpleChannelInboundHandler; |
|||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
|||
import io.netty.util.ReferenceCountUtil; |
|||
import tech.abc.platform.cip.common.entity.BaseMessage; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.abc.platform.cip.common.enums.MessageTypeEnum; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.sender.MessageSenderFactory; |
|||
import tech.popsoft.cip.client.framework.sender.ResponseMessageSender; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageTopicService; |
|||
|
|||
|
|||
/** |
|||
* 去重处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-19 |
|||
**/ |
|||
public class DistinctMessageHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { |
|||
|
|||
private ApiMessageLogService apiMessageLogService = SpringUtil.getBean(ApiMessageLogService.class); |
|||
|
|||
|
|||
private ApiMessageTopicService apiMessageTopicService = SpringUtil.getBean(ApiMessageTopicService.class); |
|||
|
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception { |
|||
String content = textWebSocketFrame.text(); |
|||
BaseMessage message = JSON.parseObject(content, BaseMessage.class); |
|||
String messageType = message.getMessageType(); |
|||
String messageId = message.getId(); |
|||
String topic = message.getTopic(); |
|||
boolean hasReceived = false; |
|||
if (messageType.equals(MessageTypeEnum.REQUEST.name())) { |
|||
hasReceived = apiMessageLogService.checkRequestMessageExist(messageId); |
|||
if (hasReceived) { |
|||
// 发送响应,终止流程
|
|||
String responseTopic = apiMessageTopicService.getResponseTopicCodeByCode(topic); |
|||
ResponseMessageSender sender = (ResponseMessageSender) MessageSenderFactory.createSender(responseTopic); |
|||
ApiMessageLog log = apiMessageLogService.getByRequestMessageId(messageId); |
|||
RequestMessage reqeustMessage = JSON.parseObject(content, RequestMessage.class); |
|||
sender.sendMessage(ctx.channel(), reqeustMessage); |
|||
|
|||
|
|||
} else { |
|||
// 继续往下传递
|
|||
ReferenceCountUtil.retain(textWebSocketFrame); |
|||
ctx.fireChannelRead(textWebSocketFrame); |
|||
} |
|||
|
|||
} else if (messageType.equals(MessageTypeEnum.RESPONSE.name())) { |
|||
hasReceived = apiMessageLogService.checkResponseMessageExist(messageId); |
|||
if (hasReceived) { |
|||
// 不做处理,终止流程
|
|||
} else { |
|||
// 继续往下传递
|
|||
ReferenceCountUtil.retain(textWebSocketFrame); |
|||
ctx.fireChannelRead(textWebSocketFrame); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,52 @@ |
|||
package tech.popsoft.cip.client.framework.customhandler; |
|||
|
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.channel.SimpleChannelInboundHandler; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.handler.MessageHandlerFactory; |
|||
import tech.popsoft.cip.client.framework.handler.RequestMessageHandler; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
import tech.popsoft.cip.client.sender.response.system.ErrorResponseSender; |
|||
|
|||
|
|||
/** |
|||
* 客户端请求消息业务逻辑处理 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-2-5 |
|||
**/ |
|||
@Slf4j |
|||
public class RequestMessageBusinessHandler extends SimpleChannelInboundHandler<RequestMessage> { |
|||
|
|||
private ApiMessageLogService apiMessageLogService = SpringUtil.getBean(ApiMessageLogService.class); |
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, RequestMessage message) throws Exception { |
|||
String requestMessageId = StringUtils.EMPTY; |
|||
String topic = StringUtils.EMPTY; |
|||
try { |
|||
|
|||
// 获取消息主题
|
|||
topic = message.getTopic(); |
|||
// 获取消息标识
|
|||
requestMessageId = message.getId(); |
|||
// 转具体的消息处理器进行处理
|
|||
RequestMessageHandler handler = (RequestMessageHandler) MessageHandlerFactory.createHandler(topic); |
|||
handler.handleMessage(message, ctx.channel()); |
|||
} catch (Exception e) { |
|||
// 记录消息请求日志
|
|||
apiMessageLogService.createRequestPart(message); |
|||
String errorMessage = e.getMessage(); |
|||
// 统一响应错误
|
|||
ErrorResponseSender errorResponseSender = new ErrorResponseSender(); |
|||
errorResponseSender.setErrorMessage(e.getMessage()); |
|||
errorResponseSender.sendMessage(ctx.channel(), message); |
|||
log.error("发生异常:", e); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,54 @@ |
|||
package tech.popsoft.cip.client.framework.customhandler; |
|||
|
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.channel.SimpleChannelInboundHandler; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.handler.MessageHandlerFactory; |
|||
import tech.popsoft.cip.client.framework.handler.ResponseMessageHandler; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
|
|||
|
|||
/** |
|||
* 客户端响应消息业务逻辑处理 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-2-5 |
|||
**/ |
|||
@Slf4j |
|||
public class ResponseMessageBusinessHandler extends SimpleChannelInboundHandler<ResponseMessage> { |
|||
|
|||
private ApiMessageLogService apiMessageLogService = SpringUtil.getBean(ApiMessageLogService.class); |
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, ResponseMessage message) throws Exception { |
|||
String requestMessageId = StringUtils.EMPTY; |
|||
String topic = StringUtils.EMPTY; |
|||
try { |
|||
|
|||
// 获取消息主题
|
|||
topic = message.getTopic(); |
|||
// 获取消息标识
|
|||
requestMessageId = message.getId(); |
|||
// 转具体的消息处理器进行处理
|
|||
ResponseMessageHandler handler = (ResponseMessageHandler) MessageHandlerFactory.createHandler(topic); |
|||
handler.handleMessage(message, ctx.channel()); |
|||
} catch (Exception e) { |
|||
// 更新消息日志
|
|||
apiMessageLogService.updateResponsePart(message); |
|||
// 对于错误响应,只记录日志,不再向发送方反馈响应
|
|||
log.error("收到客户端的响应消息有误:", e); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { |
|||
log.error("发生异常", cause); |
|||
|
|||
} |
|||
} |
@ -0,0 +1,120 @@ |
|||
package tech.popsoft.cip.client.framework.customhandler; |
|||
|
|||
import io.netty.channel.*; |
|||
import io.netty.handler.codec.http.DefaultHttpHeaders; |
|||
import io.netty.handler.codec.http.FullHttpResponse; |
|||
import io.netty.handler.codec.http.websocketx.*; |
|||
import io.netty.util.CharsetUtil; |
|||
import io.netty.util.ReferenceCountUtil; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.MessageClientConfig; |
|||
import tech.popsoft.cip.client.sender.request.system.LoginRequestSender; |
|||
|
|||
import java.net.URI; |
|||
import java.net.URISyntaxException; |
|||
|
|||
/** |
|||
* 处理web socket握手 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-9-28 |
|||
**/ |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Slf4j |
|||
@Data |
|||
public class WebSocketClientHandshakerHandler extends SimpleChannelInboundHandler<Object> { |
|||
/** |
|||
* 握手 |
|||
*/ |
|||
private WebSocketClientHandshaker handshaker; |
|||
/** |
|||
* 握手 异步处理 |
|||
*/ |
|||
private ChannelPromise handshakeFuture; |
|||
|
|||
public WebSocketClientHandshakerHandler() { |
|||
// 初始化握手处理者
|
|||
MessageClientConfig config = SpringUtil.getBean(MessageClientConfig.class); |
|||
URI webSocketUri = null; |
|||
try { |
|||
webSocketUri = new URI(config.getWebSocketUrl()); |
|||
} catch (URISyntaxException e) { |
|||
log.error("解析远程服务器地址出错", e); |
|||
} |
|||
WebSocketClientHandshaker webSocketClientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(webSocketUri, |
|||
WebSocketVersion.V13, (String) null, true, new DefaultHttpHeaders()); |
|||
this.setHandshaker(webSocketClientHandshaker); |
|||
|
|||
|
|||
} |
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { |
|||
|
|||
Channel ch = ctx.channel(); |
|||
FullHttpResponse response; |
|||
// 进行握手操作
|
|||
if (!this.handshaker.isHandshakeComplete()) { |
|||
try { |
|||
response = (FullHttpResponse) msg; |
|||
// 握手协议返回,设置结束握手
|
|||
this.handshaker.finishHandshake(ch, response); |
|||
// 设置成功
|
|||
this.handshakeFuture.setSuccess(); |
|||
|
|||
} catch (WebSocketHandshakeException var7) { |
|||
FullHttpResponse res = (FullHttpResponse) msg; |
|||
String errorMsg = String.format("握手失败,status:%s,reason:%s", res.status(), res.content().toString(CharsetUtil.UTF_8)); |
|||
this.handshakeFuture.setFailure(new Exception(errorMsg)); |
|||
} |
|||
} else if (msg instanceof FullHttpResponse) { |
|||
// 已握手成功并将http协议升级为了WebSocket协议,不应再收到Http消息,发生这种情况则抛出异常
|
|||
response = (FullHttpResponse) msg; |
|||
throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); |
|||
} else if (msg instanceof CloseWebSocketFrame) { |
|||
log.info("收到关闭信息"); |
|||
|
|||
} else if (msg instanceof TextWebSocketFrame) { |
|||
ReferenceCountUtil.retain(msg); |
|||
ctx.fireChannelRead(msg); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void handlerAdded(ChannelHandlerContext ctx) { |
|||
|
|||
|
|||
this.handshakeFuture = ctx.newPromise(); |
|||
this.handshakeFuture.addListener(new ChannelFutureListener() { |
|||
@Override |
|||
public void operationComplete(ChannelFuture future) throws Exception { |
|||
if (future.isSuccess()) { |
|||
// 发送登录请求
|
|||
log.info("握手成功"); |
|||
LoginRequestSender login = new LoginRequestSender(); |
|||
login.sendMessage(null); |
|||
|
|||
} else { |
|||
// 握手失败
|
|||
log.error("握手失败", future.cause()); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
|
|||
} |
|||
|
|||
/** |
|||
* 发起握手 |
|||
*/ |
|||
public void handshake(Channel channel) { |
|||
this.getHandshaker().handshake(channel); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,45 @@ |
|||
package tech.popsoft.cip.client.framework.handler; |
|||
|
|||
|
|||
import tech.abc.platform.cip.common.entity.MessageException; |
|||
import tech.abc.platform.common.enums.StatusEnum; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageTopic; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageTopicService; |
|||
|
|||
|
|||
/** |
|||
* 消息处理抽象类 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-13 |
|||
**/ |
|||
public class MessageHandler { |
|||
|
|||
|
|||
protected ApiMessageLogService apiMessageLogService = SpringUtil.getBean(ApiMessageLogService.class); |
|||
|
|||
protected ApiMessageTopicService apiMessageTopicService = SpringUtil.getBean(ApiMessageTopicService.class); |
|||
|
|||
|
|||
/** |
|||
* 验证主题编码 |
|||
* |
|||
* @param topicCode 主题编码 |
|||
*/ |
|||
protected void validateTopic(String topicCode) { |
|||
try { |
|||
|
|||
ApiMessageTopic messageTopic = apiMessageTopicService.getByCode(topicCode); |
|||
if (messageTopic.getStatus().equals(StatusEnum.DEAD.name())) { |
|||
throw new MessageException("S102", "消息主题不可用"); |
|||
} |
|||
} catch (Exception ex) { |
|||
throw new MessageException("S101", "消息主题不存在"); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
package tech.popsoft.cip.client.framework.handler; |
|||
|
|||
|
|||
import tech.abc.platform.cip.common.entity.MessageException; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageTopicService; |
|||
import tech.popsoft.cip.client.platform.exception.CustomException; |
|||
|
|||
|
|||
/** |
|||
* 消息处理器工厂 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-13 |
|||
**/ |
|||
public class MessageHandlerFactory { |
|||
|
|||
|
|||
public static MessageHandler createHandler(String topic) { |
|||
// 使用反射技术获取类
|
|||
Class<MessageHandler> messageHandler = null; |
|||
try { |
|||
// 根据消息主题获取对应的消息处理类名
|
|||
ApiMessageTopicService service = SpringUtil.getBean(ApiMessageTopicService.class); |
|||
String handlerName = service.getHandlerByCode(topic); |
|||
messageHandler = (Class<MessageHandler>) Class.forName(handlerName); |
|||
// 返回消息处理类的实例
|
|||
return messageHandler.newInstance(); |
|||
} catch (CustomException e) { |
|||
throw new MessageException("S101", e.getMessage()); |
|||
} catch (Exception e) { |
|||
throw new MessageException("S102", "消息处理器不存在"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
package tech.popsoft.cip.client.framework.handler; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.popsoft.cip.client.framework.sender.MessageSenderFactory; |
|||
import tech.popsoft.cip.client.framework.sender.ResponseMessageSender; |
|||
|
|||
/** |
|||
* 请求消息处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-8 |
|||
**/ |
|||
@Slf4j |
|||
public class RequestMessageHandler extends MessageHandler { |
|||
/** |
|||
* 消息处理 |
|||
* |
|||
* @param message 消息 |
|||
* @param channel 通道 |
|||
*/ |
|||
public void handleMessage(RequestMessage requestMessage, Channel channel) { |
|||
|
|||
// 记录消息请求日志
|
|||
apiMessageLogService.createRequestPart(requestMessage); |
|||
|
|||
|
|||
// 消息主题验证(是否存在及是否可用)
|
|||
validateTopic(requestMessage.getTopic()); |
|||
|
|||
// 特殊处理
|
|||
messageOperation(requestMessage, channel); |
|||
|
|||
// 发送响应至消息发送者
|
|||
sendResponse(requestMessage, channel); |
|||
|
|||
} |
|||
|
|||
|
|||
private void sendResponse(RequestMessage requestMessage, Channel channel) { |
|||
// 获取响应消息的消息主题
|
|||
String responseTopicCode = getResponseTopicCode(requestMessage.getTopic()); |
|||
// 根据消息主题构建发送器
|
|||
ResponseMessageSender responseMessageSender = (ResponseMessageSender) MessageSenderFactory.createSender(responseTopicCode); |
|||
// 发送消息
|
|||
responseMessageSender.sendMessage(channel, requestMessage); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 获取响应消息主题 |
|||
* |
|||
* @return |
|||
*/ |
|||
protected String getResponseTopicCode(String topic) { |
|||
// 默认从消息主题实体类中获取
|
|||
return apiMessageTopicService.getResponseTopicCodeByCode(topic); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 请求消息处理 |
|||
* |
|||
* @param message |
|||
* @param channel |
|||
*/ |
|||
protected void messageOperation(RequestMessage message, Channel channel) { |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,47 @@ |
|||
package tech.popsoft.cip.client.framework.handler; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
|
|||
/** |
|||
* 响应消息处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-8 11:07 |
|||
**/ |
|||
@Slf4j |
|||
public class ResponseMessageHandler extends MessageHandler { |
|||
|
|||
/** |
|||
* 消息处理 |
|||
* |
|||
* @param message 消息 |
|||
* @param channel 通道 |
|||
*/ |
|||
public void handleMessage(ResponseMessage responseMessage, Channel channel) { |
|||
|
|||
|
|||
// 消息主题验证(是否存在及是否可用)
|
|||
validateTopic(responseMessage.getTopic()); |
|||
|
|||
// 更新消息日志
|
|||
apiMessageLogService.updateResponsePart(responseMessage); |
|||
|
|||
// 特殊处理
|
|||
messageOperation(responseMessage, channel); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 响应消息处理 |
|||
* |
|||
* @param message |
|||
* @param channel |
|||
*/ |
|||
protected void messageOperation(ResponseMessage message, Channel channel) { |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,69 @@ |
|||
package tech.popsoft.cip.client.framework.sender; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.MessageClientConfig; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
|
|||
|
|||
/** |
|||
* 消息发送器基类 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-5 14:02 |
|||
*/ |
|||
@Slf4j |
|||
public class MessageSender { |
|||
|
|||
public MessageSender() { |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 消息主题 |
|||
*/ |
|||
private String topic; |
|||
/** |
|||
* 消息内容 |
|||
*/ |
|||
private String content; |
|||
|
|||
public MessageSender(String topic) { |
|||
this.topic = topic; |
|||
} |
|||
|
|||
/** |
|||
* 获取消息主题 |
|||
*/ |
|||
public String getTopic() { |
|||
return this.topic; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 设置消息内容 |
|||
* |
|||
* @param content |
|||
*/ |
|||
public void setContent(String content) { |
|||
this.content = content; |
|||
} |
|||
|
|||
/** |
|||
* 获取消息内容 |
|||
*/ |
|||
public String getContent() { |
|||
return this.content; |
|||
} |
|||
|
|||
/** |
|||
* 应用程序配置 |
|||
*/ |
|||
protected MessageClientConfig messageClientConfig = SpringUtil.getBean(MessageClientConfig.class); |
|||
|
|||
/** |
|||
* 消息日志服务 |
|||
*/ |
|||
protected ApiMessageLogService apiMessageLogService = SpringUtil.getBean(ApiMessageLogService.class); |
|||
|
|||
} |
@ -0,0 +1,38 @@ |
|||
package tech.popsoft.cip.client.framework.sender; |
|||
|
|||
|
|||
import tech.abc.platform.cip.common.entity.MessageException; |
|||
import tech.abc.platform.common.exception.CustomException; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageTopicService; |
|||
|
|||
|
|||
/** |
|||
* 消息发送器工厂 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-01-29 16:30 |
|||
**/ |
|||
public class MessageSenderFactory { |
|||
private MessageSenderFactory() { |
|||
} |
|||
|
|||
; |
|||
|
|||
public static MessageSender createSender(String topic) { |
|||
// 使用反射技术获取类
|
|||
Class<MessageSender> messageSender = null; |
|||
try { |
|||
// 根据消息主题获取对应的消息处理类名
|
|||
ApiMessageTopicService service = SpringUtil.getBean(ApiMessageTopicService.class); |
|||
String senderName = service.getSenderByCode(topic); |
|||
messageSender = (Class<MessageSender>) Class.forName(senderName); |
|||
// 返回消息发送器类的实例
|
|||
return messageSender.newInstance(); |
|||
} catch (CustomException e) { |
|||
throw new MessageException("S101", e.getMessage()); |
|||
} catch (Exception e) { |
|||
throw new MessageException("S102", "消息发送器不存在"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
package tech.popsoft.cip.client.framework.sender; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import io.netty.channel.ChannelFuture; |
|||
import io.netty.channel.ChannelFutureListener; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.abc.platform.cip.common.enums.MessageStatusEnum; |
|||
import tech.popsoft.cip.client.framework.MessageClientGlobalHolder; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
|
|||
|
|||
/** |
|||
* 请求消息发送器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-5 |
|||
*/ |
|||
@Slf4j |
|||
public class RequestMessageSender extends MessageSender { |
|||
|
|||
|
|||
public RequestMessageSender(String topic) { |
|||
super(topic); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 发送消息 |
|||
* |
|||
* @param content 消息内容 |
|||
* @param id 消息标识 |
|||
*/ |
|||
public void sendMessage(String content, String id) { |
|||
// 生成请求消息
|
|||
RequestMessage message = new RequestMessage(); |
|||
// 使用已有ID重置默认生成的ID,用于关联消息
|
|||
if (StringUtils.isNotBlank(id)) { |
|||
message.setId(id); |
|||
} |
|||
// 设置相关属性
|
|||
message.setTopic(super.getTopic()); |
|||
// 参数中消息内容优先,如为空,取对象属性的值
|
|||
if (StringUtils.isNotBlank(content)) { |
|||
message.setContent(content); |
|||
} else { |
|||
message.setContent(this.getContent()); |
|||
} |
|||
|
|||
message.setPublishAppCode(messageClientConfig.getAppCode()); |
|||
|
|||
|
|||
// 获取发送通道
|
|||
Channel channel = MessageClientGlobalHolder.channel; |
|||
if (channel != null && channel.isActive()) { |
|||
ChannelFuture channelFuture = channel.writeAndFlush(message); |
|||
channelFuture.addListener(new ChannelFutureListener() { |
|||
@Override |
|||
public void operationComplete(ChannelFuture future) throws Exception { |
|||
|
|||
if (StringUtils.isBlank(id)) { |
|||
// 创建日志
|
|||
ApiMessageLog log = apiMessageLogService.createRequestPart(message); |
|||
// 设置状态为已请求
|
|||
apiMessageLogService.updateStatus(MessageStatusEnum.REQUESTED.name(), log.getRequestId()); |
|||
// 发送次数加1
|
|||
apiMessageLogService.incrementSendCount(log.getRequestId()); |
|||
} else { |
|||
// 更新发送次数
|
|||
apiMessageLogService.incrementSendCount(id); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
} else { |
|||
// 创建日志
|
|||
apiMessageLogService.createRequestPart(message); |
|||
} |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 发送请求消息 |
|||
* |
|||
* @param content 消息内容 |
|||
*/ |
|||
public void sendMessage(String content) { |
|||
// 发送消息
|
|||
sendMessage(content, null); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 发送请求消息 |
|||
* |
|||
* @param content 消息内容 |
|||
*/ |
|||
public void sendMessage() { |
|||
// 发送消息
|
|||
sendMessage(null, null); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,74 @@ |
|||
package tech.popsoft.cip.client.framework.sender; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
import tech.abc.platform.cip.common.enums.MessageResponseResultEnum; |
|||
|
|||
/** |
|||
* 响应消息发送器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-31 8:14 |
|||
**/ |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Data |
|||
public class ResponseMessageSender extends MessageSender { |
|||
|
|||
private String result; |
|||
|
|||
private String errorCode; |
|||
|
|||
private String errorMessage; |
|||
|
|||
|
|||
public ResponseMessageSender(String topic) { |
|||
super(topic); |
|||
// 默认设置响应结果为成功
|
|||
this.result = MessageResponseResultEnum.SUCCESS.name(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 发送响应 |
|||
* |
|||
* @param channel 通道 |
|||
* @param requestMessage 请求消息 |
|||
*/ |
|||
public void sendMessage(Channel channel, RequestMessage requestMessage) { |
|||
|
|||
// 组织响应消息
|
|||
ResponseMessage responseMessage = new ResponseMessage(); |
|||
responseMessage.setPublishAppCode(messageClientConfig.getAppCode()); |
|||
responseMessage.setRequestMessageId(requestMessage.getId()); |
|||
// 设置主题
|
|||
responseMessage.setTopic(this.getTopic()); |
|||
// 设置默认值
|
|||
responseMessage.setResult(this.result); |
|||
responseMessage.setErrorCode(this.errorCode); |
|||
responseMessage.setErrorMessage(this.errorMessage); |
|||
// 设置响应
|
|||
setResponseContent(requestMessage, responseMessage, channel); |
|||
// 发送响应给请求方
|
|||
channel.writeAndFlush(responseMessage); |
|||
|
|||
// 更新消息日志
|
|||
apiMessageLogService.updateResponsePart(responseMessage); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 设置响应消息内容 |
|||
* |
|||
* @param requestMessage 请求消息 |
|||
* @param responseMessage 响应消息 |
|||
* @param channel 通道 |
|||
*/ |
|||
protected void setResponseContent(RequestMessage requestMessage, ResponseMessage responseMessage, Channel channel) { |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,24 @@ |
|||
package tech.popsoft.cip.client.handler.request.lms.transportbill; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.popsoft.cip.client.framework.handler.RequestMessageHandler; |
|||
|
|||
/** |
|||
* 委托单创建请求消息处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-22 |
|||
**/ |
|||
@Slf4j |
|||
public class ConsignmentBillCreateRequestHandler extends RequestMessageHandler { |
|||
|
|||
@Override |
|||
protected void messageOperation(RequestMessage message, Channel channel) { |
|||
|
|||
// 进行业务处理
|
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package tech.popsoft.cip.client.handler.response.system; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
import tech.popsoft.cip.client.framework.handler.ResponseMessageHandler; |
|||
|
|||
/** |
|||
* 错误响应处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-16 8:53 |
|||
**/ |
|||
@Slf4j |
|||
public class ErrorResponseHandler extends ResponseMessageHandler { |
|||
@Override |
|||
protected void messageOperation(ResponseMessage message, Channel channel) { |
|||
log.info("收到错误响应"); |
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
package tech.popsoft.cip.client.handler.response.system; |
|||
|
|||
import io.netty.channel.Channel; |
|||
import io.netty.channel.ChannelFuture; |
|||
import io.netty.channel.EventLoop; |
|||
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
import tech.abc.platform.cip.common.enums.MessageResponseResultEnum; |
|||
import tech.abc.platform.common.utils.SpringUtil; |
|||
import tech.popsoft.cip.client.framework.MessageClientConfig; |
|||
import tech.popsoft.cip.client.framework.MessageClientGlobalHolder; |
|||
import tech.popsoft.cip.client.framework.handler.ResponseMessageHandler; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* 登录响应处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-16 |
|||
**/ |
|||
@Slf4j |
|||
public class LoginResponseHandler extends ResponseMessageHandler { |
|||
@Override |
|||
protected void messageOperation(ResponseMessage message, Channel channel) { |
|||
log.info("收到登录响应"); |
|||
|
|||
if (message.getResult().equals(MessageResponseResultEnum.SUCCESS.name())) { |
|||
// 登录成功,将连接保存到全局变量
|
|||
MessageClientGlobalHolder.channel = channel; |
|||
// 启动心跳
|
|||
startHeartbeat(channel); |
|||
|
|||
} else { |
|||
// 登录失败,关闭通道,重新连接
|
|||
log.error("登录失败:{}", message.getErrorMessage()); |
|||
channel.close(); |
|||
} |
|||
} |
|||
|
|||
private void startHeartbeat(Channel channel) { |
|||
// 获取心跳发送频率
|
|||
MessageClientConfig config = SpringUtil.getBean(MessageClientConfig.class); |
|||
int heartbeatRate = config.getHeartbeatRate(); |
|||
// 启动心跳
|
|||
EventLoop eventLoop = channel.eventLoop(); |
|||
eventLoop.scheduleWithFixedDelay(new Runnable() { |
|||
private Channel channel; |
|||
|
|||
|
|||
@Override |
|||
public void run() { |
|||
// log.info("发送心跳");
|
|||
PingWebSocketFrame frame = new PingWebSocketFrame(); |
|||
ChannelFuture channelFuture = channel.writeAndFlush(frame); |
|||
|
|||
} |
|||
|
|||
public Runnable setChannel(Channel channel) { |
|||
this.channel = channel; |
|||
return this; |
|||
} |
|||
|
|||
}.setChannel(channel), 15, heartbeatRate, TimeUnit.SECONDS); |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
package tech.popsoft.cip.client.handler.response.system; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import tech.popsoft.cip.client.framework.handler.ResponseMessageHandler; |
|||
|
|||
/** |
|||
* 消息确认 响应处理器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-16 |
|||
**/ |
|||
@Slf4j |
|||
public class MessageConfirmResponseHandler extends ResponseMessageHandler { |
|||
|
|||
} |
@ -0,0 +1,185 @@ |
|||
package tech.popsoft.cip.client.manage.controller; |
|||
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import io.swagger.annotations.ApiImplicitParam; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.validation.annotation.Validated; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import tech.abc.platform.common.annotation.SystemLog; |
|||
import tech.abc.platform.common.base.BaseController; |
|||
import tech.abc.platform.common.query.QueryGenerator; |
|||
import tech.abc.platform.common.utils.ResultUtil; |
|||
import tech.abc.platform.common.vo.PageInfo; |
|||
import tech.abc.platform.common.vo.Result; |
|||
import tech.abc.platform.common.vo.SortInfo; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
import tech.popsoft.cip.client.manage.vo.ApiMessageLogVO; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 消息日志 前端控制器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@RestController |
|||
@RequestMapping("/cip/apiMessageLog") |
|||
@Slf4j |
|||
public class ApiMessageLogController extends BaseController { |
|||
@Autowired |
|||
private ApiMessageLogService apiMessageLogService; |
|||
|
|||
// region 基本操作
|
|||
|
|||
/** |
|||
* 初始化 |
|||
*/ |
|||
@ApiOperation(value = "初始化") |
|||
@GetMapping("/init") |
|||
public ResponseEntity<Result> init() { |
|||
ApiMessageLog entity = apiMessageLogService.init(); |
|||
ApiMessageLogVO vo = convert2VO(entity); |
|||
return ResultUtil.success(vo); |
|||
} |
|||
|
|||
/** |
|||
* 新增 |
|||
*/ |
|||
@ApiOperation(value = "新增") |
|||
@PostMapping("/") |
|||
@SystemLog(value = "消息日志-新增") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageLog:add')") |
|||
public ResponseEntity<Result> add(@Validated @RequestBody ApiMessageLogVO vo) { |
|||
ApiMessageLog entity = convert2Entity(vo); |
|||
apiMessageLogService.add(entity); |
|||
ApiMessageLogVO newVO = convert2VO(entity); |
|||
return ResultUtil.success(newVO); |
|||
} |
|||
|
|||
/** |
|||
* 修改 |
|||
*/ |
|||
@ApiOperation(value = "修改") |
|||
@PutMapping("/") |
|||
@SystemLog(value = "消息日志-修改") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageLog:modify')") |
|||
public ResponseEntity<Result> modify(@Validated @RequestBody ApiMessageLogVO vo) { |
|||
ApiMessageLog entity = convert2Entity(vo); |
|||
// 此处数据库会更新 更新人和更新时间,但数据模型不会刷新
|
|||
// 个别需展示的该类信息的地方可以重新查询后返回
|
|||
apiMessageLogService.modify(entity); |
|||
ApiMessageLogVO newVO = convert2VO(entity); |
|||
return ResultUtil.success(newVO); |
|||
} |
|||
|
|||
/** |
|||
* 删除数据,单条数据标识,或多条数据标识用逗号间隔拼成的字符串 |
|||
*/ |
|||
@ApiOperation(value = "删除") |
|||
@ApiImplicitParam(name = "id", value = "标识", required = true, dataType = "String", paramType = "path") |
|||
@DeleteMapping("/{id}") |
|||
@SystemLog(value = "消息日志-删除") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageLog:remove')") |
|||
public ResponseEntity<Result> remove(@PathVariable("id") String id) { |
|||
apiMessageLogService.remove(id); |
|||
return ResultUtil.success(); |
|||
} |
|||
|
|||
/** |
|||
* 分页 |
|||
*/ |
|||
@ApiOperation(value = "分页查询") |
|||
@GetMapping("/page") |
|||
@SystemLog(value = "消息日志-分页") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageLog:query')") |
|||
public ResponseEntity<Result> page(ApiMessageLogVO queryVO, PageInfo pageInfo, SortInfo sortInfo) { |
|||
|
|||
// 构造分页对象
|
|||
IPage<ApiMessageLog> page = new Page<ApiMessageLog>(pageInfo.getPageNum(), pageInfo.getPageSize()); |
|||
// 构造查询条件
|
|||
QueryWrapper<ApiMessageLog> queryWrapper = QueryGenerator.generateQueryWrapper(ApiMessageLog.class, queryVO, sortInfo); |
|||
|
|||
// 查询数据
|
|||
apiMessageLogService.page(page, queryWrapper); |
|||
// 转换vo
|
|||
IPage<ApiMessageLogVO> pageVO = mapperFacade.map(page, IPage.class); |
|||
List<ApiMessageLogVO> apiMessageLogVOList = new ArrayList<>(); |
|||
for (int i = 0; i < page.getRecords().size(); i++) { |
|||
ApiMessageLogVO vo = convert2VO(page.getRecords().get(i)); |
|||
apiMessageLogVOList.add(vo); |
|||
} |
|||
pageVO.setRecords(apiMessageLogVOList); |
|||
; |
|||
return ResultUtil.success(pageVO); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 列表 |
|||
*/ |
|||
@ApiOperation(value = "获取列表") |
|||
@GetMapping("/list") |
|||
@SystemLog(value = "消息日志-列表") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageLog:query')") |
|||
public ResponseEntity<Result> list(ApiMessageLogVO queryVO, SortInfo sortInfo) { |
|||
// 构造查询条件
|
|||
QueryWrapper<ApiMessageLog> queryWrapper = QueryGenerator.generateQueryWrapper(ApiMessageLog.class, queryVO, sortInfo); |
|||
List<ApiMessageLog> list = apiMessageLogService.list(queryWrapper); |
|||
// 转换vo
|
|||
List<ApiMessageLogVO> apiMessageLogVOList = new ArrayList<>(); |
|||
for (int i = 0; i < list.size(); i++) { |
|||
ApiMessageLogVO vo = convert2VO(list.get(i)); |
|||
apiMessageLogVOList.add(vo); |
|||
} |
|||
return ResultUtil.success(apiMessageLogVOList); |
|||
} |
|||
|
|||
/** |
|||
* 获取单条数据 |
|||
*/ |
|||
@ApiOperation(value = "获取单条数据") |
|||
@ApiImplicitParam(name = "id", value = "标识", required = true, dataType = "String", paramType = "path") |
|||
@GetMapping("/{id}") |
|||
@SystemLog(value = "消息日志-详情") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageLog:view')") |
|||
public ResponseEntity<Result> get(@PathVariable("id") String id) { |
|||
ApiMessageLog entity = apiMessageLogService.query(id); |
|||
ApiMessageLogVO vo = convert2VO(entity); |
|||
return ResultUtil.success(vo); |
|||
} |
|||
|
|||
// endregion
|
|||
|
|||
// region 扩展操作
|
|||
|
|||
// endregion
|
|||
|
|||
// region 辅助操作
|
|||
|
|||
private ApiMessageLogVO convert2VO(ApiMessageLog entity) { |
|||
ApiMessageLogVO vo = mapperFacade.map(entity, ApiMessageLogVO.class); |
|||
String name = dictionaryUtil.getNameByCode("MessageResponseResult", entity.getResponseResult()); |
|||
vo.setResponseResultName(name); |
|||
String statusName = dictionaryUtil.getNameByCode("MessageStatus", entity.getResponseResult()); |
|||
vo.setStatusName(statusName); |
|||
|
|||
return vo; |
|||
} |
|||
|
|||
private ApiMessageLog convert2Entity(ApiMessageLogVO vo) { |
|||
|
|||
ApiMessageLog entity = mapperFacade.map(vo, ApiMessageLog.class); |
|||
return entity; |
|||
} |
|||
|
|||
// endregion
|
|||
} |
@ -0,0 +1,224 @@ |
|||
package tech.popsoft.cip.client.manage.controller; |
|||
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import io.swagger.annotations.ApiImplicitParam; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.validation.annotation.Validated; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import tech.abc.platform.common.annotation.SystemLog; |
|||
import tech.abc.platform.common.base.BaseController; |
|||
import tech.abc.platform.common.query.QueryGenerator; |
|||
import tech.abc.platform.common.utils.ResultUtil; |
|||
import tech.abc.platform.common.vo.PageInfo; |
|||
import tech.abc.platform.common.vo.Result; |
|||
import tech.abc.platform.common.vo.SortInfo; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageTopic; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageTopicService; |
|||
import tech.popsoft.cip.client.manage.vo.ApiMessageTopicVO; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 消息主题 前端控制器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@RestController |
|||
@RequestMapping("/cip/apiMessageTopic") |
|||
@Slf4j |
|||
public class ApiMessageTopicController extends BaseController { |
|||
@Autowired |
|||
private ApiMessageTopicService apiMessageTopicService; |
|||
|
|||
// region 基本操作
|
|||
|
|||
/** |
|||
* 初始化 |
|||
*/ |
|||
@ApiOperation(value = "初始化") |
|||
@GetMapping("/init") |
|||
public ResponseEntity<Result> init() { |
|||
ApiMessageTopic entity = apiMessageTopicService.init(); |
|||
ApiMessageTopicVO vo = convert2VO(entity); |
|||
return ResultUtil.success(vo); |
|||
} |
|||
|
|||
/** |
|||
* 新增 |
|||
*/ |
|||
@ApiOperation(value = "新增") |
|||
@PostMapping("/") |
|||
@SystemLog(value = "消息主题-新增") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:add')") |
|||
public ResponseEntity<Result> add(@Validated @RequestBody ApiMessageTopicVO vo) { |
|||
ApiMessageTopic entity = convert2Entity(vo); |
|||
apiMessageTopicService.add(entity); |
|||
ApiMessageTopicVO newVO = convert2VO(entity); |
|||
return ResultUtil.success(newVO); |
|||
} |
|||
|
|||
/** |
|||
* 修改 |
|||
*/ |
|||
@ApiOperation(value = "修改") |
|||
@PutMapping("/") |
|||
@SystemLog(value = "消息主题-修改") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:modify')") |
|||
public ResponseEntity<Result> modify(@Validated @RequestBody ApiMessageTopicVO vo) { |
|||
ApiMessageTopic entity = convert2Entity(vo); |
|||
// 此处数据库会更新 更新人和更新时间,但数据模型不会刷新
|
|||
// 个别需展示的该类信息的地方可以重新查询后返回
|
|||
apiMessageTopicService.modify(entity); |
|||
ApiMessageTopicVO newVO = convert2VO(entity); |
|||
return ResultUtil.success(newVO); |
|||
} |
|||
|
|||
/** |
|||
* 删除数据,单条数据标识,或多条数据标识用逗号间隔拼成的字符串 |
|||
*/ |
|||
@ApiOperation(value = "删除") |
|||
@ApiImplicitParam(name = "id", value = "标识", required = true, dataType = "String", paramType = "path") |
|||
@DeleteMapping("/{id}") |
|||
@SystemLog(value = "消息主题-删除") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:remove')") |
|||
public ResponseEntity<Result> remove(@PathVariable("id") String id) { |
|||
apiMessageTopicService.remove(id); |
|||
return ResultUtil.success(); |
|||
} |
|||
|
|||
/** |
|||
* 分页 |
|||
*/ |
|||
@ApiOperation(value = "分页查询") |
|||
@GetMapping("/page") |
|||
@SystemLog(value = "消息主题-分页") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:query')") |
|||
public ResponseEntity<Result> page(ApiMessageTopicVO queryVO, PageInfo pageInfo, SortInfo sortInfo) { |
|||
|
|||
// 构造分页对象
|
|||
IPage<ApiMessageTopic> page = new Page<ApiMessageTopic>(pageInfo.getPageNum(), pageInfo.getPageSize()); |
|||
// 构造查询条件
|
|||
QueryWrapper<ApiMessageTopic> queryWrapper = QueryGenerator.generateQueryWrapper(ApiMessageTopic.class, queryVO, sortInfo); |
|||
|
|||
// 查询数据
|
|||
apiMessageTopicService.page(page, queryWrapper); |
|||
// 转换vo
|
|||
IPage<ApiMessageTopicVO> pageVO = mapperFacade.map(page, IPage.class); |
|||
List<ApiMessageTopicVO> apiMessageTopicVOList = new ArrayList<>(); |
|||
for (int i = 0; i < page.getRecords().size(); i++) { |
|||
ApiMessageTopicVO vo = convert2VO(page.getRecords().get(i)); |
|||
apiMessageTopicVOList.add(vo); |
|||
} |
|||
pageVO.setRecords(apiMessageTopicVOList); |
|||
; |
|||
return ResultUtil.success(pageVO); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 列表 |
|||
*/ |
|||
@ApiOperation(value = "获取列表") |
|||
@GetMapping("/list") |
|||
@SystemLog(value = "消息主题-列表") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:query')") |
|||
public ResponseEntity<Result> list(ApiMessageTopicVO queryVO, SortInfo sortInfo) { |
|||
// 构造查询条件
|
|||
QueryWrapper<ApiMessageTopic> queryWrapper = QueryGenerator.generateQueryWrapper(ApiMessageTopic.class, queryVO, sortInfo); |
|||
List<ApiMessageTopic> list = apiMessageTopicService.list(queryWrapper); |
|||
// 转换vo
|
|||
List<ApiMessageTopicVO> apiMessageTopicVOList = new ArrayList<>(); |
|||
for (int i = 0; i < list.size(); i++) { |
|||
ApiMessageTopicVO vo = convert2VO(list.get(i)); |
|||
apiMessageTopicVOList.add(vo); |
|||
} |
|||
return ResultUtil.success(apiMessageTopicVOList); |
|||
} |
|||
|
|||
/** |
|||
* 获取单条数据 |
|||
*/ |
|||
@ApiOperation(value = "获取单条数据") |
|||
@ApiImplicitParam(name = "id", value = "标识", required = true, dataType = "String", paramType = "path") |
|||
@GetMapping("/{id}") |
|||
@SystemLog(value = "消息主题-详情") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:view')") |
|||
public ResponseEntity<Result> get(@PathVariable("id") String id) { |
|||
ApiMessageTopic entity = apiMessageTopicService.query(id); |
|||
ApiMessageTopicVO vo = convert2VO(entity); |
|||
return ResultUtil.success(vo); |
|||
} |
|||
|
|||
// endregion
|
|||
|
|||
// region 扩展操作
|
|||
|
|||
/** |
|||
* 启用 |
|||
*/ |
|||
@ApiOperation(value = "启用") |
|||
@PutMapping("/{id}/enable") |
|||
@ApiImplicitParam(name = "id", value = "标识", required = true, dataType = "String", paramType = "path") |
|||
@SystemLog(value = "消息主题-启用") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:enable')") |
|||
public ResponseEntity<Result> enable(@PathVariable("id") String id) { |
|||
|
|||
apiMessageTopicService.enable(id); |
|||
return ResultUtil.success(); |
|||
} |
|||
|
|||
/** |
|||
* 停用 |
|||
*/ |
|||
@ApiOperation(value = "停用") |
|||
@PutMapping("/{id}/disable") |
|||
@ApiImplicitParam(name = "id", value = "标识", required = true, dataType = "String", paramType = "path") |
|||
@SystemLog(value = "消息主题-停用") |
|||
@PreAuthorize("hasPermission(null,'cip:apiMessageTopic:disable')") |
|||
public ResponseEntity<Result> disable(@PathVariable("id") String id) { |
|||
|
|||
apiMessageTopicService.disable(id); |
|||
return ResultUtil.success(); |
|||
} |
|||
|
|||
|
|||
// endregion
|
|||
|
|||
// region 辅助操作
|
|||
|
|||
private ApiMessageTopicVO convert2VO(ApiMessageTopic entity) { |
|||
ApiMessageTopicVO vo = mapperFacade.map(entity, ApiMessageTopicVO.class); |
|||
String statusName = dictionaryUtil.getNameByCode("Status", entity.getStatus()); |
|||
vo.setStatusName(statusName); |
|||
String categoryName = dictionaryUtil.getNameByCode("ApiMessageTopicCategory", entity.getCategory()); |
|||
vo.setCategoryName(categoryName); |
|||
|
|||
if (StringUtils.isNotBlank(entity.getHasPermission())) { |
|||
String hasPermission = dictionaryUtil.getNameByCode("YesOrNo", entity.getHasPermission()); |
|||
vo.setHasPermissionName(hasPermission); |
|||
} |
|||
|
|||
if (StringUtils.isNotBlank(entity.getHasSubscription())) { |
|||
String hasSubscription = dictionaryUtil.getNameByCode("YesOrNo", entity.getHasSubscription()); |
|||
vo.setHasSubscriptionName(hasSubscription); |
|||
} |
|||
return vo; |
|||
} |
|||
|
|||
private ApiMessageTopic convert2Entity(ApiMessageTopicVO vo) { |
|||
|
|||
ApiMessageTopic entity = mapperFacade.map(vo, ApiMessageTopic.class); |
|||
return entity; |
|||
} |
|||
|
|||
// endregion
|
|||
} |
@ -0,0 +1,116 @@ |
|||
package tech.popsoft.cip.client.manage.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.experimental.Accessors; |
|||
import tech.abc.platform.common.base.BaseEntity; |
|||
|
|||
import java.time.LocalDateTime; |
|||
|
|||
/** |
|||
* 消息日志 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Accessors(chain = true) |
|||
@TableName("cip_message_log") |
|||
public class ApiMessageLog extends BaseEntity { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 请求消息标识 |
|||
*/ |
|||
@TableField("request_id") |
|||
private String requestId; |
|||
|
|||
/** |
|||
* 请求应用编码 |
|||
*/ |
|||
@TableField("request_app_code") |
|||
private String requestAppCode; |
|||
|
|||
/** |
|||
* 请求消息主题编码 |
|||
*/ |
|||
@TableField("request_topic_code") |
|||
private String requestTopicCode; |
|||
|
|||
/** |
|||
* 请求时间 |
|||
*/ |
|||
@TableField("request_time") |
|||
private LocalDateTime requestTime; |
|||
|
|||
/** |
|||
* 请求内容 |
|||
*/ |
|||
@TableField("request_data") |
|||
private String requestData; |
|||
|
|||
/** |
|||
* 响应消息标识 |
|||
*/ |
|||
@TableField("response_id") |
|||
private String responseId; |
|||
|
|||
/** |
|||
* 响应应用编码 |
|||
*/ |
|||
@TableField("response_app_code") |
|||
private String responseAppCode; |
|||
|
|||
/** |
|||
* 响应消息主题编码 |
|||
*/ |
|||
@TableField("response_topic_code") |
|||
private String responseTopicCode; |
|||
|
|||
/** |
|||
* 响应时间 |
|||
*/ |
|||
@TableField("response_time") |
|||
private LocalDateTime responseTime; |
|||
|
|||
/** |
|||
* 响应内容 |
|||
*/ |
|||
@TableField("response_data") |
|||
private String responseData; |
|||
|
|||
/** |
|||
* 响应结果 |
|||
*/ |
|||
@TableField("response_result") |
|||
private String responseResult; |
|||
|
|||
/** |
|||
* 错误编码 |
|||
*/ |
|||
@TableField("error_code") |
|||
private String errorCode; |
|||
|
|||
/** |
|||
* 错误信息 |
|||
*/ |
|||
@TableField("error_message") |
|||
private String errorMessage; |
|||
|
|||
/** |
|||
* 当前状态 |
|||
*/ |
|||
@TableField("status") |
|||
private String status; |
|||
|
|||
/** |
|||
* 发送次数 |
|||
*/ |
|||
@TableField("send_count") |
|||
private Integer sendCount; |
|||
|
|||
} |
@ -0,0 +1,93 @@ |
|||
package tech.popsoft.cip.client.manage.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.experimental.Accessors; |
|||
import tech.abc.platform.common.base.BaseEntity; |
|||
|
|||
|
|||
/** |
|||
* 消息主题 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Accessors(chain = true) |
|||
@TableName("cip_message_topic") |
|||
public class ApiMessageTopic extends BaseEntity { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 编码 |
|||
*/ |
|||
@TableField("code") |
|||
private String code; |
|||
|
|||
/** |
|||
* 名称 |
|||
*/ |
|||
@TableField("name") |
|||
private String name; |
|||
|
|||
/** |
|||
* 处理器 |
|||
*/ |
|||
@TableField("handler") |
|||
private String handler; |
|||
|
|||
|
|||
/** |
|||
* 发送器 |
|||
*/ |
|||
@TableField("sender") |
|||
private String sender; |
|||
|
|||
|
|||
/** |
|||
* 响应主题编码 |
|||
*/ |
|||
@TableField("response_topic_code") |
|||
private String responseTopicCode; |
|||
|
|||
/** |
|||
* 分类 |
|||
*/ |
|||
@TableField("category") |
|||
private String category; |
|||
|
|||
/** |
|||
* 状态 |
|||
*/ |
|||
@TableField("status") |
|||
private String status; |
|||
|
|||
/** |
|||
* 备注 |
|||
*/ |
|||
@TableField("remark") |
|||
private String remark; |
|||
|
|||
/** |
|||
* 排序号 |
|||
*/ |
|||
@TableField("order_no") |
|||
private String orderNo; |
|||
|
|||
/** |
|||
* 是否已授权 |
|||
*/ |
|||
@TableField(exist = false) |
|||
private String hasPermission; |
|||
|
|||
/** |
|||
* 是否已订阅 |
|||
*/ |
|||
@TableField(exist = false) |
|||
private String hasSubscription; |
|||
|
|||
} |
@ -0,0 +1,28 @@ |
|||
package tech.popsoft.cip.client.manage.exception; |
|||
|
|||
import lombok.Getter; |
|||
import tech.abc.platform.common.exception.ExceptionInterface; |
|||
|
|||
|
|||
/** |
|||
* 消息主题相关异常 |
|||
* |
|||
* @author wqliu |
|||
* @date: 2020-03-29 19:11 |
|||
*/ |
|||
@Getter |
|||
public enum ApiMessageLogExceptionEnum implements ExceptionInterface { |
|||
|
|||
/** |
|||
* 消息日志不存在 |
|||
*/ |
|||
MESSAGE_LOG_NOT_EXIST("消息日志不存在"), |
|||
|
|||
; |
|||
private String message; |
|||
|
|||
ApiMessageLogExceptionEnum(String message) { |
|||
this.message = message; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,39 @@ |
|||
package tech.popsoft.cip.client.manage.exception; |
|||
|
|||
import lombok.Getter; |
|||
import tech.abc.platform.common.exception.ExceptionInterface; |
|||
|
|||
|
|||
/** |
|||
* 消息主题相关异常 |
|||
* |
|||
* @author wqliu |
|||
* @date: 2020-03-29 19:11 |
|||
*/ |
|||
@Getter |
|||
public enum ApiMessageTopicExceptionEnum implements ExceptionInterface { |
|||
|
|||
/** |
|||
* 消息主题不存在 |
|||
*/ |
|||
TOPIC_NOT_EXIST("消息主题不存在"), |
|||
/** |
|||
* 消息主题未设置处理器 |
|||
*/ |
|||
TOPIC_NOT_SET_HANDLER("消息主题未设置处理器"), |
|||
/** |
|||
* 消息主题未设置发送器 |
|||
*/ |
|||
TOPIC_NOT_SET_SENDER("消息主题未设置发送器"), |
|||
/** |
|||
* 响应消息主题未设置处理器 |
|||
*/ |
|||
RESPONSE_TOPIC_NOT_SET_HANDLER("响应消息主题未设置处理器"), |
|||
; |
|||
private String message; |
|||
|
|||
ApiMessageTopicExceptionEnum(String message) { |
|||
this.message = message; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,15 @@ |
|||
package tech.popsoft.cip.client.manage.mapper; |
|||
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
|
|||
|
|||
/** |
|||
* 消息日志 Mapper 接口 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
public interface ApiMessageLogMapper extends BaseMapper<ApiMessageLog> { |
|||
|
|||
} |
@ -0,0 +1,5 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="tech.popsoft.cip.client.manage.mapper.ApiMessageLogMapper"> |
|||
|
|||
</mapper> |
@ -0,0 +1,15 @@ |
|||
package tech.popsoft.cip.client.manage.mapper; |
|||
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageTopic; |
|||
|
|||
|
|||
/** |
|||
* 消息主题 Mapper 接口 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
public interface ApiMessageTopicMapper extends BaseMapper<ApiMessageTopic> { |
|||
|
|||
} |
@ -0,0 +1,5 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="tech.popsoft.cip.client.manage.mapper.ApiMessageTopicMapper"> |
|||
|
|||
</mapper> |
@ -0,0 +1,92 @@ |
|||
package tech.popsoft.cip.client.manage.service; |
|||
|
|||
|
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
import tech.abc.platform.common.base.BaseService; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
|
|||
import java.util.List; |
|||
|
|||
|
|||
/** |
|||
* 消息日志 服务类 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
public interface ApiMessageLogService extends BaseService<ApiMessageLog> { |
|||
|
|||
/** |
|||
* 检查请求消息是否已存在 |
|||
* |
|||
* @param requestMessageId 请求消息标识 |
|||
* @return true 存在 false 不存在 |
|||
*/ |
|||
boolean checkRequestMessageExist(String requestMessageId); |
|||
|
|||
/** |
|||
* 检查响应消息是否已存在 |
|||
* |
|||
* @param responseMessageId 响应消息标识 |
|||
* @return true 存在 false 不存在 |
|||
*/ |
|||
boolean checkResponseMessageExist(String responseMessageId); |
|||
|
|||
/** |
|||
* 根据请求消息标识获取消息日志对象 |
|||
* |
|||
* @param requestMessageId 请求消息标识 |
|||
* @return 消息日志对象 |
|||
*/ |
|||
ApiMessageLog getByRequestMessageId(String requestMessageId); |
|||
|
|||
/** |
|||
* 更新状态 |
|||
* |
|||
* @param status 状态 |
|||
* @param messageId 消息标识 |
|||
*/ |
|||
void updateStatus(String status, String messageId); |
|||
|
|||
/** |
|||
* 递增发送计数 |
|||
* |
|||
* @param messageId 消息标识 |
|||
*/ |
|||
void incrementSendCount(String messageId); |
|||
|
|||
|
|||
/** |
|||
* 创建日志,填充请求消息部分 |
|||
* |
|||
* @param message |
|||
*/ |
|||
ApiMessageLog createRequestPart(RequestMessage message); |
|||
|
|||
/** |
|||
* 创建日志,填充请求消息部分 |
|||
* |
|||
* @param message |
|||
* @param responseAppCode 响应应用编码 |
|||
*/ |
|||
ApiMessageLog createRequestPart(RequestMessage message, String responseAppCode); |
|||
|
|||
|
|||
/** |
|||
* 更新日志,填充响应消息部分 |
|||
* |
|||
* @param message |
|||
*/ |
|||
void updateResponsePart(ResponseMessage message); |
|||
|
|||
|
|||
/** |
|||
* 获取重发消息 |
|||
* |
|||
* @param messageCount 消息数量 |
|||
* @param maxSendCount 最大发送次数 |
|||
* @return {@link List}<{@link ApiMessageLog}> |
|||
*/ |
|||
List<ApiMessageLog> getResendMessage(int messageCount, int maxSendCount); |
|||
} |
@ -0,0 +1,72 @@ |
|||
package tech.popsoft.cip.client.manage.service; |
|||
|
|||
import tech.abc.platform.common.base.BaseService; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageTopic; |
|||
|
|||
|
|||
/** |
|||
* 消息主题 服务类 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
public interface ApiMessageTopicService extends BaseService<ApiMessageTopic> { |
|||
|
|||
/** |
|||
* 启用 |
|||
* |
|||
* @param id 标识 |
|||
*/ |
|||
void enable(String id); |
|||
|
|||
/** |
|||
* 停用 |
|||
* |
|||
* @param id 标识 |
|||
*/ |
|||
void disable(String id); |
|||
|
|||
|
|||
/** |
|||
* 根据消息主题编码获取处理类 |
|||
* |
|||
* @param code 消息主题 |
|||
* @return 处理类名(含路径) |
|||
*/ |
|||
String getHandlerByCode(String code); |
|||
|
|||
/** |
|||
* 根据消息主题编码获取响应主题编码 |
|||
* |
|||
* @param code 消息主题 |
|||
* @return 响应主题编码 |
|||
*/ |
|||
String getResponseTopicCodeByCode(String code); |
|||
|
|||
|
|||
/** |
|||
* 根据消息主题编码获取消息主题标识 |
|||
* |
|||
* @param code 消息主题编码 |
|||
* @return 消息主题标识 |
|||
*/ |
|||
String getIdByCode(String code); |
|||
|
|||
|
|||
/** |
|||
* 根据消息主题编码获取消息主题对象 |
|||
* |
|||
* @param code 消息主题编码 |
|||
* @return 消息主题对象 |
|||
*/ |
|||
ApiMessageTopic getByCode(String code); |
|||
|
|||
/** |
|||
* 根据消息主题编码获取发送类 |
|||
* |
|||
* @param code 消息主题 |
|||
* @return 发送类名(含路径) |
|||
*/ |
|||
String getSenderByCode(String code); |
|||
|
|||
} |
@ -0,0 +1,146 @@ |
|||
package tech.popsoft.cip.client.manage.service.impl; |
|||
|
|||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.collections.CollectionUtils; |
|||
import org.springframework.stereotype.Service; |
|||
import tech.abc.platform.cip.common.entity.RequestMessage; |
|||
import tech.abc.platform.cip.common.entity.ResponseMessage; |
|||
import tech.abc.platform.cip.common.enums.MessageStatusEnum; |
|||
import tech.abc.platform.common.base.BaseServiceImpl; |
|||
import tech.abc.platform.common.exception.CustomException; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageLog; |
|||
import tech.popsoft.cip.client.manage.exception.ApiMessageLogExceptionEnum; |
|||
import tech.popsoft.cip.client.manage.mapper.ApiMessageLogMapper; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageLogService; |
|||
|
|||
import java.time.LocalDateTime; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 消息日志 服务实现类 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class ApiMessageLogServiceImpl extends BaseServiceImpl<ApiMessageLogMapper, ApiMessageLog> implements ApiMessageLogService { |
|||
@Override |
|||
public ApiMessageLog init() { |
|||
ApiMessageLog entity = new ApiMessageLog(); |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
@Override |
|||
public boolean checkRequestMessageExist(String requestMessageId) { |
|||
long count = this.lambdaQuery().eq(ApiMessageLog::getRequestId, requestMessageId).count(); |
|||
return count > 0; |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public boolean checkResponseMessageExist(String responseMessageId) { |
|||
long count = this.lambdaQuery().eq(ApiMessageLog::getResponseId, responseMessageId).count(); |
|||
return count > 0; |
|||
} |
|||
|
|||
@Override |
|||
public ApiMessageLog getByRequestMessageId(String requestMessageId) { |
|||
List<ApiMessageLog> list = this.lambdaQuery().eq(ApiMessageLog::getRequestId, requestMessageId).list(); |
|||
if (CollectionUtils.isNotEmpty(list)) { |
|||
return list.get(0); |
|||
|
|||
} else { |
|||
throw new CustomException(ApiMessageLogExceptionEnum.MESSAGE_LOG_NOT_EXIST); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void updateStatus(String status, String messageId) { |
|||
ApiMessageLog apiMessageLog = getByRequestMessageId(messageId); |
|||
apiMessageLog.setStatus(status); |
|||
modify(apiMessageLog); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public ApiMessageLog createRequestPart(RequestMessage message, String responseAppCode) { |
|||
|
|||
ApiMessageLog log = new ApiMessageLog(); |
|||
log.setRequestId(message.getId()); |
|||
log.setRequestAppCode(message.getPublishAppCode()); |
|||
log.setRequestTopicCode(message.getTopic()); |
|||
log.setRequestTime(LocalDateTime.now()); |
|||
log.setRequestData(message.getContent()); |
|||
log.setResponseAppCode(responseAppCode); |
|||
// 发送次数设置为0
|
|||
log.setSendCount(0); |
|||
add(log); |
|||
return log; |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public ApiMessageLog createRequestPart(RequestMessage message) { |
|||
return createRequestPart(message, null); |
|||
} |
|||
|
|||
@Override |
|||
public void updateResponsePart(ResponseMessage message) { |
|||
|
|||
ApiMessageLog log = getByRequestMessageId(message.getRequestMessageId()); |
|||
// 响应消息的发布者,对应请求消息的响应
|
|||
log.setResponseAppCode(message.getPublishAppCode()); |
|||
log.setResponseTopicCode(message.getTopic()); |
|||
log.setResponseTime(LocalDateTime.now()); |
|||
log.setResponseData(message.getContent()); |
|||
log.setResponseResult(message.getResult()); |
|||
log.setErrorMessage(message.getErrorMessage()); |
|||
log.setErrorCode(message.getErrorCode()); |
|||
log.setResponseId(message.getId()); |
|||
// 将消息更新为已响应
|
|||
log.setStatus(MessageStatusEnum.RESPONSED.name()); |
|||
|
|||
modify(log); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public List<ApiMessageLog> getResendMessage(int messageCount, int maxSendCount) { |
|||
LocalDateTime now = LocalDateTime.now(); |
|||
// 获取当前时间之前15秒的时间点
|
|||
LocalDateTime beforeNow = now.minusSeconds(15); |
|||
try { |
|||
LambdaQueryChainWrapper<ApiMessageLog> query = this.lambdaQuery() |
|||
// 消息状态为待发送或已发送
|
|||
.and(x -> x.eq(ApiMessageLog::getStatus, MessageStatusEnum.WAIT_REQUEST.name()) |
|||
.or(y -> y.eq(ApiMessageLog::getStatus, MessageStatusEnum.REQUESTED.name()))) |
|||
// 排除掉登录请求
|
|||
.ne(ApiMessageLog::getRequestTopicCode, "framework.login.request") |
|||
// 发送次数小于设置的最大发送次数
|
|||
.lt(ApiMessageLog::getSendCount, maxSendCount) |
|||
// 请求时间小于当前时间15秒,避免刚产生的消息尚未收到服务端响应时就进行重发
|
|||
.lt(ApiMessageLog::getRequestTime, beforeNow) |
|||
// 按照请求时间升序排列
|
|||
.orderByAsc(ApiMessageLog::getRequestTime) |
|||
// 只取指定数量的消息
|
|||
.last("limit " + messageCount); |
|||
|
|||
|
|||
return query.list(); |
|||
} catch (Exception e) { |
|||
log.error("获取发送信息失败", e); |
|||
return null; |
|||
} |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void incrementSendCount(String messageId) { |
|||
ApiMessageLog apiMessageLog = getByRequestMessageId(messageId); |
|||
apiMessageLog.setSendCount(apiMessageLog.getSendCount() + 1); |
|||
modify(apiMessageLog); |
|||
} |
|||
} |
@ -0,0 +1,150 @@ |
|||
package tech.popsoft.cip.client.manage.service.impl; |
|||
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|||
import org.apache.commons.collections.CollectionUtils; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.stereotype.Service; |
|||
import tech.abc.platform.common.base.BaseServiceImpl; |
|||
import tech.abc.platform.common.enums.StatusEnum; |
|||
import tech.abc.platform.common.exception.CommonException; |
|||
import tech.abc.platform.common.exception.CustomException; |
|||
import tech.popsoft.cip.client.manage.entity.ApiMessageTopic; |
|||
import tech.popsoft.cip.client.manage.exception.ApiMessageTopicExceptionEnum; |
|||
import tech.popsoft.cip.client.manage.mapper.ApiMessageTopicMapper; |
|||
import tech.popsoft.cip.client.manage.service.ApiMessageTopicService; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 消息主题 服务实现类 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@Service |
|||
public class ApiMessageTopicServiceImpl extends BaseServiceImpl<ApiMessageTopicMapper, ApiMessageTopic> implements ApiMessageTopicService { |
|||
|
|||
|
|||
@Override |
|||
public ApiMessageTopic init() { |
|||
ApiMessageTopic entity = new ApiMessageTopic(); |
|||
entity.setStatus(StatusEnum.NORMAL.name()); |
|||
return entity; |
|||
} |
|||
|
|||
@Override |
|||
public void beforeAdd(ApiMessageTopic entity) { |
|||
|
|||
|
|||
// 验证编码全局唯一
|
|||
QueryWrapper<ApiMessageTopic> queryWrapper = new QueryWrapper<>(); |
|||
queryWrapper.lambda().eq(ApiMessageTopic::getCode, entity.getCode()); |
|||
long count = count(queryWrapper); |
|||
if (count > 0) { |
|||
throw new CustomException(CommonException.CODE_EXIST); |
|||
} |
|||
|
|||
|
|||
// 验证名称全局唯一
|
|||
queryWrapper.lambda().eq(ApiMessageTopic::getName, entity.getName()); |
|||
count = count(queryWrapper); |
|||
if (count > 0) { |
|||
throw new CustomException(CommonException.NAME_EXIST); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void beforeModify(ApiMessageTopic entity) { |
|||
|
|||
|
|||
// 验证编码全局唯一
|
|||
QueryWrapper<ApiMessageTopic> queryWrapper = new QueryWrapper<>(); |
|||
queryWrapper.lambda().eq(ApiMessageTopic::getCode, entity.getCode()) |
|||
.ne(ApiMessageTopic::getId, entity.getId()); |
|||
long count = count(queryWrapper); |
|||
if (count > 0) { |
|||
throw new CustomException(CommonException.CODE_EXIST); |
|||
} |
|||
|
|||
|
|||
// 验证名称全局唯一
|
|||
queryWrapper.lambda().eq(ApiMessageTopic::getName, entity.getName()) |
|||
.ne(ApiMessageTopic::getId, entity.getId()); |
|||
count = count(queryWrapper); |
|||
if (count > 0) { |
|||
throw new CustomException(CommonException.NAME_EXIST); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void enable(String id) { |
|||
ApiMessageTopic entity = getEntity(id); |
|||
entity.setStatus(StatusEnum.NORMAL.name()); |
|||
modify(entity); |
|||
} |
|||
|
|||
@Override |
|||
public void disable(String id) { |
|||
ApiMessageTopic entity = getEntity(id); |
|||
entity.setStatus(StatusEnum.DEAD.name()); |
|||
modify(entity); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public String getHandlerByCode(String code) { |
|||
List<ApiMessageTopic> list = this.lambdaQuery().eq(ApiMessageTopic::getCode, code).list(); |
|||
if (CollectionUtils.isEmpty(list)) { |
|||
throw new CustomException(ApiMessageTopicExceptionEnum.TOPIC_NOT_EXIST); |
|||
} |
|||
String handler = list.get(0).getHandler(); |
|||
if (StringUtils.isBlank(handler)) { |
|||
throw new CustomException(ApiMessageTopicExceptionEnum.TOPIC_NOT_SET_HANDLER); |
|||
} |
|||
return handler; |
|||
} |
|||
|
|||
@Override |
|||
public String getResponseTopicCodeByCode(String code) { |
|||
List<ApiMessageTopic> list = this.lambdaQuery().eq(ApiMessageTopic::getCode, code).list(); |
|||
if (CollectionUtils.isEmpty(list)) { |
|||
throw new CustomException(ApiMessageTopicExceptionEnum.TOPIC_NOT_EXIST); |
|||
} |
|||
String responseTopicCode = list.get(0).getResponseTopicCode(); |
|||
if (StringUtils.isBlank(responseTopicCode)) { |
|||
throw new CustomException(ApiMessageTopicExceptionEnum.RESPONSE_TOPIC_NOT_SET_HANDLER); |
|||
} |
|||
return responseTopicCode; |
|||
} |
|||
|
|||
@Override |
|||
public String getIdByCode(String code) { |
|||
return getByCode(code).getId(); |
|||
} |
|||
|
|||
@Override |
|||
public ApiMessageTopic getByCode(String code) { |
|||
List<ApiMessageTopic> list = this.lambdaQuery().eq(ApiMessageTopic::getCode, code).list(); |
|||
if (CollectionUtils.isNotEmpty(list)) { |
|||
return list.get(0); |
|||
|
|||
} else { |
|||
throw new CustomException(ApiMessageTopicExceptionEnum.TOPIC_NOT_EXIST); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public String getSenderByCode(String code) { |
|||
ApiMessageTopic apiMessageTopic = getByCode(code); |
|||
String sender = apiMessageTopic.getSender(); |
|||
if (StringUtils.isBlank(sender)) { |
|||
throw new CustomException(ApiMessageTopicExceptionEnum.TOPIC_NOT_SET_SENDER); |
|||
} |
|||
return sender; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,83 @@ |
|||
package tech.popsoft.cip.client.manage.vo; |
|||
|
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.experimental.Accessors; |
|||
import tech.abc.platform.common.base.BaseVO; |
|||
|
|||
import java.time.LocalDateTime; |
|||
|
|||
/** |
|||
* 消息日志 视图对象 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Accessors(chain = true) |
|||
|
|||
@ApiModel(value = "消息日志对象") |
|||
public class ApiMessageLogVO extends BaseVO { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "请求消息标识") |
|||
private String requestId; |
|||
|
|||
@ApiModelProperty(value = "请求应用编码") |
|||
private String requestAppCode; |
|||
|
|||
@ApiModelProperty(value = "请求消息主题编码") |
|||
private String requestTopicCode; |
|||
|
|||
@ApiModelProperty(value = "请求时间") |
|||
private LocalDateTime requestTime; |
|||
|
|||
@ApiModelProperty(value = "请求内容") |
|||
private String requestData; |
|||
|
|||
@ApiModelProperty(value = "响应消息标识") |
|||
private String responseId; |
|||
|
|||
@ApiModelProperty(value = "响应应用编码") |
|||
private String responseAppCode; |
|||
|
|||
@ApiModelProperty(value = "响应消息主题编码") |
|||
private String responseTopicCode; |
|||
|
|||
@ApiModelProperty(value = "响应时间") |
|||
private LocalDateTime responseTime; |
|||
|
|||
@ApiModelProperty(value = "响应内容") |
|||
private String responseData; |
|||
|
|||
@ApiModelProperty(value = "响应结果") |
|||
private String responseResult; |
|||
|
|||
@ApiModelProperty(value = "错误编码") |
|||
private String errorCode; |
|||
|
|||
@ApiModelProperty(value = "错误信息") |
|||
private String errorMessage; |
|||
|
|||
@ApiModelProperty(value = "当前状态") |
|||
private String status; |
|||
|
|||
@ApiModelProperty(value = "发送次数") |
|||
private Integer sendCount; |
|||
/********自定义扩展*****/ |
|||
|
|||
/********字典类*****/ |
|||
@ApiModelProperty(value = "消息状态") |
|||
private String responseResultName; |
|||
|
|||
@ApiModelProperty(value = "响应结果") |
|||
private String statusName; |
|||
/********子对象*****/ |
|||
|
|||
|
|||
} |
@ -0,0 +1,78 @@ |
|||
package tech.popsoft.cip.client.manage.vo; |
|||
|
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.experimental.Accessors; |
|||
import tech.abc.platform.common.base.BaseVO; |
|||
|
|||
|
|||
/** |
|||
* 消息主题 视图对象 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-08-21 |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Accessors(chain = true) |
|||
|
|||
@ApiModel(value = "消息主题对象") |
|||
public class ApiMessageTopicVO extends BaseVO { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "编码") |
|||
private String code; |
|||
|
|||
@ApiModelProperty(value = "名称") |
|||
private String name; |
|||
|
|||
@ApiModelProperty(value = "处理器") |
|||
private String handler; |
|||
|
|||
@ApiModelProperty(value = "发送器") |
|||
private String sender; |
|||
|
|||
@ApiModelProperty(value = "响应主题编码") |
|||
private String responseTopicCode; |
|||
|
|||
@ApiModelProperty(value = "分类") |
|||
private String category; |
|||
|
|||
@ApiModelProperty(value = "状态") |
|||
private String status; |
|||
|
|||
@ApiModelProperty(value = "备注") |
|||
private String remark; |
|||
|
|||
@ApiModelProperty(value = "排序号") |
|||
private String orderNo; |
|||
/********自定义扩展*****/ |
|||
@ApiModelProperty(value = "应用标识") |
|||
private String appId; |
|||
|
|||
@ApiModelProperty(value = "是否已授权") |
|||
private String hasPermission; |
|||
|
|||
@ApiModelProperty(value = "是否已订阅") |
|||
private String hasSubscription; |
|||
/********字典类*****/ |
|||
@ApiModelProperty(value = "状态") |
|||
private String statusName; |
|||
|
|||
@ApiModelProperty(value = "分类") |
|||
private String categoryName; |
|||
|
|||
@ApiModelProperty(value = "是否已授权") |
|||
private String hasPermissionName; |
|||
|
|||
@ApiModelProperty(value = "是否已订阅") |
|||
private String hasSubscriptionName; |
|||
|
|||
/********子对象*****/ |
|||
|
|||
|
|||
} |
@ -0,0 +1,27 @@ |
|||
package tech.popsoft.cip.client.platform.exception; |
|||
|
|||
|
|||
import java.text.MessageFormat; |
|||
|
|||
/** |
|||
* 自定义异常 |
|||
* |
|||
* @author wqliu |
|||
*/ |
|||
public class CustomException extends RuntimeException { |
|||
|
|||
/** |
|||
* 继承exception |
|||
*/ |
|||
public CustomException(ExceptionInterface exceptionInterface) { |
|||
super(exceptionInterface.getMessage()); |
|||
} |
|||
|
|||
/** |
|||
* 错误信息带参数 |
|||
*/ |
|||
public CustomException(ExceptionInterface exceptionInterface, Object... args) { |
|||
super(MessageFormat.format(exceptionInterface.getMessage(), args)); |
|||
|
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
package tech.popsoft.cip.client.platform.exception; |
|||
|
|||
/** |
|||
* 异常接口 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-8-1 |
|||
*/ |
|||
public interface ExceptionInterface { |
|||
/** |
|||
* 获取异常信息 |
|||
* |
|||
* @return 异常信息 |
|||
*/ |
|||
String getMessage(); |
|||
} |
@ -0,0 +1,37 @@ |
|||
package tech.popsoft.cip.client.receiver; |
|||
|
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import tech.abc.platform.cip.common.entity.MessageException; |
|||
import tech.popsoft.cip.client.framework.sender.MessageSenderFactory; |
|||
import tech.popsoft.cip.client.framework.sender.RequestMessageSender; |
|||
|
|||
/** |
|||
* 接收业务系统产生的消息 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-18 |
|||
**/ |
|||
@RestController |
|||
@RequestMapping("/message") |
|||
public class ReceiveMessageController { |
|||
|
|||
@GetMapping |
|||
public String receive(String topic, String id) { |
|||
// 数据验证
|
|||
if (StringUtils.isBlank(topic)) { |
|||
throw new RuntimeException("消息主题不能为空"); |
|||
} |
|||
// 根据消息主题查找发送器
|
|||
try { |
|||
RequestMessageSender messageSender = (RequestMessageSender) MessageSenderFactory.createSender(topic); |
|||
messageSender.sendMessage(id); |
|||
return "ok"; |
|||
} catch (MessageException e) { |
|||
throw new RuntimeException(e.getMessage()); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
package tech.popsoft.cip.client.sender.request.lms.transportbill; |
|||
|
|||
import tech.popsoft.cip.client.framework.sender.RequestMessageSender; |
|||
|
|||
/** |
|||
* 委托单创建发送器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2022-1-18 |
|||
**/ |
|||
public class ConsignmentBillCreateSender extends RequestMessageSender { |
|||
|
|||
public ConsignmentBillCreateSender() { |
|||
super("lms.transportbill.consignmentbill.create"); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,31 @@ |
|||
package tech.popsoft.cip.client.sender.request.system; |
|||
|
|||
import com.alibaba.fastjson.JSONObject; |
|||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
|||
import tech.popsoft.cip.client.framework.sender.RequestMessageSender; |
|||
|
|||
/** |
|||
* 登录请求发送器 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-6 |
|||
**/ |
|||
public class LoginRequestSender extends RequestMessageSender { |
|||
|
|||
|
|||
public LoginRequestSender() { |
|||
super("framework.login.request"); |
|||
|
|||
// 使用账号密钥构建消息内容
|
|||
JSONObject jsonObject = new JSONObject(); |
|||
jsonObject.put("appCode", messageClientConfig.getAppCode()); |
|||
// 对密钥进行加密处理
|
|||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); |
|||
String password = encoder.encode(messageClientConfig.getAppSecret()); |
|||
jsonObject.put("appSecret", password); |
|||
super.setContent(jsonObject.toJSONString()); |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,25 @@ |
|||
package tech.popsoft.cip.client.sender.response.system; |
|||
|
|||
|
|||
import tech.abc.platform.cip.common.enums.MessageResponseResultEnum; |
|||
import tech.popsoft.cip.client.framework.sender.ResponseMessageSender; |
|||
|
|||
|
|||
/** |
|||
* 向请求方发送发生错误的响应 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-14 10:38 |
|||
**/ |
|||
public class ErrorResponseSender extends ResponseMessageSender { |
|||
|
|||
public ErrorResponseSender() { |
|||
super("framework.error.response"); |
|||
// 默认设置结果为出错
|
|||
this.setResult(MessageResponseResultEnum.ERROR.name()); |
|||
// 默认设置错误编码
|
|||
this.setErrorCode("500"); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,20 @@ |
|||
package tech.popsoft.cip.client.sender.response.system; |
|||
|
|||
|
|||
import tech.popsoft.cip.client.framework.sender.ResponseMessageSender; |
|||
|
|||
/** |
|||
* 向请求方发送发生消息确认的响应 |
|||
* |
|||
* @author wqliu |
|||
* @date 2021-10-14 10:38 |
|||
**/ |
|||
public class MessageConfirmResponseSender extends ResponseMessageSender { |
|||
|
|||
public MessageConfirmResponseSender() { |
|||
super("framework.message.confirm.response"); |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,29 @@ |
|||
server: |
|||
port: 20001 |
|||
|
|||
params: |
|||
appCode: tms |
|||
appSecret: 8543692d-7ea7-4797-a746-47891bff2bec |
|||
host: localhost |
|||
port: 8997 |
|||
heartbeatRate: 5 |
|||
maxSendCount: 4 |
|||
sendMessageSpan: 30 |
|||
sendMessageCount: 10 |
|||
enableResend: true |
|||
webSocketPath: webSocket |
|||
readIdleTimeOut: 30 |
|||
spring: |
|||
# 数据库设置 |
|||
datasource: |
|||
url: jdbc:mysql://localhost:3306/cip_client?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai |
|||
username: root |
|||
password: root |
|||
# redis设置 |
|||
redis: |
|||
host: localhost |
|||
port: 6379 |
|||
password: test123 |
|||
platform-config: |
|||
message: |
|||
serverPort: 9998 |
@ -0,0 +1,94 @@ |
|||
server: |
|||
port: 8080 |
|||
#数据连接 |
|||
spring: |
|||
datasource: |
|||
# 使用druid数据源 |
|||
type: com.alibaba.druid.pool.DruidDataSource |
|||
driver-class-name: com.mysql.cj.jdbc.Driver |
|||
url: jdbc:mysql://localhost:3306/abc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true |
|||
username: root |
|||
password: root |
|||
druid: |
|||
# 连接池的配置信息 |
|||
# 初始化大小,最小,最大 |
|||
initial-size: 5 |
|||
min-idle: 5 |
|||
maxActive: 20 |
|||
# 配置获取连接等待超时的时间 |
|||
maxWait: 60000 |
|||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 |
|||
timeBetweenEvictionRunsMillis: 600000 |
|||
# 配置一个连接在池中最小生存的时间,单位是毫秒 |
|||
minEvictableIdleTimeMillis: 300000 |
|||
validationQuery: SELECT 1 |
|||
testWhileIdle: false |
|||
testOnBorrow: true |
|||
testOnReturn: false |
|||
usePingMethod: false |
|||
devtools: |
|||
livereload: |
|||
enabled: true |
|||
redis: |
|||
host: localhost |
|||
port: 6379 |
|||
password: |
|||
#新版本redis的timeout是一个duration,需使用如下写法 |
|||
timeout: 10s |
|||
database: 0 |
|||
lettuce: |
|||
pool: |
|||
# 连接池中的最小空闲连接 |
|||
min-idle: 2 |
|||
# 连接池中的最大空闲连接 |
|||
max-idle: 2 |
|||
# 连接池的最大连接数 |
|||
max-active: 16 |
|||
#连接池最大阻塞等待时间 |
|||
max-wait: 30s |
|||
profiles: |
|||
active: dev |
|||
# session: |
|||
# store-type: REDIS |
|||
# timeout: 8h |
|||
servlet: |
|||
# 文件上传设置 |
|||
multipart: |
|||
#设置单个文件的大小,-1代表不限制 |
|||
max-file-size: -1 |
|||
#设置单次请求文件的大小,-1代表不限制 |
|||
max-request-size: -1 |
|||
|
|||
freemarker: |
|||
# 不检测是否存在模板,避免启动时控制台输出警告信息 |
|||
checkTemplateLocation: false |
|||
|
|||
|
|||
|
|||
#mybatis-plus配置 |
|||
mybatis-plus: |
|||
# mapper-locations: classpath*:/system/**.xml |
|||
global-config: |
|||
db-config: |
|||
logic-delete-value: "YES" # 逻辑已删除值 |
|||
logic-not-delete-value: "NO" # 逻辑未删除值 |
|||
#平台配置 |
|||
platform-config: |
|||
system: |
|||
enablePermission: false |
|||
userInitPassword: 12345678 |
|||
tokenSecret: wqliu |
|||
exportDataPageSize: 2 |
|||
message: |
|||
serverPort: 8997 |
|||
messageServerAppCode: CIP |
|||
readIdleTimeOut: 60 |
|||
maxSendCount: 10 |
|||
sendMessageSpan: 30 |
|||
sendMessageCount: 10 |
|||
enableResend: true |
|||
|
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,84 @@ |
|||
-- -------------------------------------------------------- |
|||
-- 主机: 127.0.0.1 |
|||
-- 服务器版本: 8.0.19 - MySQL Community Server - GPL |
|||
-- 服务器操作系统: Win64 |
|||
-- HeidiSQL 版本: 12.5.0.6677 |
|||
-- -------------------------------------------------------- |
|||
|
|||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; |
|||
/*!40101 SET NAMES utf8 */; |
|||
/*!50503 SET NAMES utf8mb4 */; |
|||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; |
|||
/*!40103 SET TIME_ZONE='+00:00' */; |
|||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; |
|||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; |
|||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; |
|||
|
|||
|
|||
-- 导出 cip_client 的数据库结构 |
|||
CREATE DATABASE IF NOT EXISTS `cip_client` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */ /*!80016 DEFAULT ENCRYPTION='N' */; |
|||
USE `cip_client`; |
|||
|
|||
-- 导出 表 cip_client.cip_message_log 结构 |
|||
CREATE TABLE IF NOT EXISTS `cip_message_log` ( |
|||
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标识', |
|||
`request_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求消息标识', |
|||
`request_app_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求应用编码', |
|||
`request_topic_code` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求消息主题编码', |
|||
`request_time` datetime DEFAULT NULL COMMENT '请求时间', |
|||
`request_data` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求内容', |
|||
`response_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应消息标识', |
|||
`response_app_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应应用编码', |
|||
`response_topic_code` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应消息主题编码', |
|||
`response_time` datetime DEFAULT NULL COMMENT '响应时间', |
|||
`response_data` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应内容', |
|||
`response_result` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应结果', |
|||
`error_code` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '错误编码', |
|||
`error_message` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '错误信息', |
|||
`status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '当前状态', |
|||
`send_count` int DEFAULT NULL COMMENT '发送次数', |
|||
`create_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人标识', |
|||
`create_time` datetime DEFAULT NULL COMMENT '创建时间', |
|||
`update_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人标识', |
|||
`update_time` datetime DEFAULT NULL COMMENT '更新时间', |
|||
`version` int DEFAULT NULL COMMENT '版本', |
|||
`delete_flag` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '删除标志', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='消息日志'; |
|||
|
|||
-- 正在导出表 cip_client.cip_message_log 的数据:~0 rows (大约) |
|||
|
|||
-- 导出 表 cip_client.cip_message_topic 结构 |
|||
CREATE TABLE IF NOT EXISTS `cip_message_topic` ( |
|||
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标识', |
|||
`code` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '编码', |
|||
`name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名称', |
|||
`handler` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '处理器', |
|||
`sender` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '发送器', |
|||
`response_topic_code` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应主题编码', |
|||
`category` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '分类', |
|||
`status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '状态', |
|||
`remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注', |
|||
`order_no` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '排序号', |
|||
`create_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人标识', |
|||
`create_time` datetime DEFAULT NULL COMMENT '创建时间', |
|||
`update_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人标识', |
|||
`update_time` datetime DEFAULT NULL COMMENT '更新时间', |
|||
`version` int DEFAULT NULL COMMENT '版本', |
|||
`delete_flag` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '删除标志', |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='消息主题'; |
|||
|
|||
-- 正在导出表 cip_client.cip_message_topic 的数据:~5 rows (大约) |
|||
INSERT INTO `cip_message_topic` (`id`, `code`, `name`, `handler`, `sender`, `response_topic_code`, `category`, `status`, `remark`, `order_no`, `create_id`, `create_time`, `update_id`, `update_time`, `version`, `delete_flag`) VALUES |
|||
('1448247847417774081', 'framework.login.response', '登录响应', 'tech.popsoft.cip.client.handler.response.system.LoginResponseHandler', NULL, '', 'USER', 'NORMAL', NULL, '01', '1', '2021-10-13 19:22:49', '1', '2021-10-13 19:22:49', 1, 'NO'), |
|||
('1448247847417774082', 'lms.transportbill.consignmentbill.create', '委托单创建', 'tech.popsoft.cip.client.handler.request.lms.transportbill.ConsignmentBillCreateRequestHandler', 'tech.popsoft.cip.client.sender.request.lms.transportbill.ConsignmentBillCreateSender', 'framework.message.confirm.response', 'USER', 'NORMAL', NULL, '01', '1', '2021-10-13 19:22:49', '1', '2021-10-13 19:22:49', 1, 'NO'), |
|||
('1448247847417774083', 'framework.error.response', '错误响应', 'tech.popsoft.cip.client.handler.response.system.ErrorResponseHandler', 'tech.popsoft.cip.client.sender.response.system.ErrorResponseSender', '', 'USER', 'NORMAL', NULL, '01', '1', '2021-10-13 19:22:49', '1', '2021-10-13 19:22:49', 1, 'NO'), |
|||
('1448247847417774084', 'framework.message.confirm.response', '消息确认响应', 'tech.popsoft.cip.client.handler.response.system.MessageConfirmResponseHandler', 'tech.popsoft.cip.client.sender.response.system.MessageConfirmResponseSender', '', 'USER', 'NORMAL', NULL, '01', '1', '2021-10-13 19:22:49', '1', '2021-10-13 19:22:49', 1, 'NO'), |
|||
('1448247847417774086', 'framework.login.request', '登录请求', '', 'tech.popsoft.cip.client.sender.request.system.LoginRequestSender', '', 'USER', 'NORMAL', NULL, '01', '1', '2021-10-13 19:22:49', '1', '2021-10-13 19:22:49', 1, 'NO'); |
|||
|
|||
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; |
|||
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; |
|||
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; |
|||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; |
|||
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; |
@ -0,0 +1,18 @@ |
|||
package tech.popsoft; |
|||
|
|||
import org.junit.Test; |
|||
|
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
/** |
|||
* Unit test for simple App. |
|||
*/ |
|||
public class AppTest { |
|||
/** |
|||
* Rigorous Test :-) |
|||
*/ |
|||
@Test |
|||
public void shouldAnswerWithTrue() { |
|||
assertTrue(true); |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
###################################################################### |
|||
# Build Tools |
|||
|
|||
/unpackage/* |
|||
/node_modules/* |
|||
|
|||
###################################################################### |
|||
# Development Tools |
|||
|
|||
/.idea/* |
|||
/.vscode/* |
|||
/.hbuilderx/* |
|||
|
|||
package-lock.json |
|||
yarn.lock |
|||
|
@ -0,0 +1,34 @@ |
|||
<script> |
|||
import config from './config' |
|||
import store from '@/store' |
|||
import { getToken } from '@/utils/auth' |
|||
|
|||
export default { |
|||
onLaunch: function() { |
|||
this.initApp() |
|||
}, |
|||
methods: { |
|||
// 初始化应用 |
|||
initApp() { |
|||
// 初始化应用配置 |
|||
this.initConfig() |
|||
// 检查用户登录状态 |
|||
//#ifdef H5 |
|||
this.checkLogin() |
|||
//#endif |
|||
}, |
|||
initConfig() { |
|||
this.globalData.config = config |
|||
}, |
|||
checkLogin() { |
|||
if (!getToken()) { |
|||
this.$tab.reLaunch('/pages/login') |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import '@/static/scss/index.scss' |
|||
</style> |
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2022 |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,50 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
// 登录方法
|
|||
export function login(username, password) { |
|||
|
|||
return request({ |
|||
'url': '/system/user/login?username='+username+'&password='+password, |
|||
'method': 'post' |
|||
}) |
|||
} |
|||
|
|||
// 注册方法
|
|||
export function register(data) { |
|||
return request({ |
|||
url: '/register', |
|||
headers: { |
|||
isToken: false |
|||
}, |
|||
method: 'post', |
|||
data: data |
|||
}) |
|||
} |
|||
|
|||
// 获取用户详细信息
|
|||
export function getInfo() { |
|||
return request({ |
|||
'url': '/getInfo', |
|||
'method': 'get' |
|||
}) |
|||
} |
|||
|
|||
// 退出方法
|
|||
export function logout() { |
|||
return request({ |
|||
'url': '/logout', |
|||
'method': 'post' |
|||
}) |
|||
} |
|||
|
|||
// 获取验证码
|
|||
export function getCodeImg() { |
|||
return request({ |
|||
'url': '/captchaImage', |
|||
headers: { |
|||
isToken: false |
|||
}, |
|||
method: 'get', |
|||
timeout: 20000 |
|||
}) |
|||
} |
@ -0,0 +1,41 @@ |
|||
import upload from '@/utils/upload' |
|||
import request from '@/utils/request' |
|||
|
|||
// 用户密码重置
|
|||
export function updateUserPwd(oldPassword, newPassword) { |
|||
const data = { |
|||
oldPassword, |
|||
newPassword |
|||
} |
|||
return request({ |
|||
url: '/system/user/profile/updatePwd', |
|||
method: 'put', |
|||
params: data |
|||
}) |
|||
} |
|||
|
|||
// 查询用户个人信息
|
|||
export function getUserProfile() { |
|||
return request({ |
|||
url: '/system/user/profile', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
// 修改用户个人信息
|
|||
export function updateUserProfile(data) { |
|||
return request({ |
|||
url: '/system/user/profile', |
|||
method: 'put', |
|||
data: data |
|||
}) |
|||
} |
|||
|
|||
// 用户头像上传
|
|||
export function uploadAvatar(data) { |
|||
return upload({ |
|||
url: '/system/user/profile/avatar', |
|||
name: data.name, |
|||
filePath: data.filePath |
|||
}) |
|||
} |
@ -0,0 +1,167 @@ |
|||
<template> |
|||
<view class="uni-section"> |
|||
<view class="uni-section-header" @click="onClick"> |
|||
<view class="uni-section-header__decoration" v-if="type" :class="type" /> |
|||
<slot v-else name="decoration"></slot> |
|||
|
|||
<view class="uni-section-header__content"> |
|||
<text :style="{'font-size':titleFontSize,'color':titleColor}" class="uni-section__content-title" :class="{'distraction':!subTitle}">{{ title }}</text> |
|||
<text v-if="subTitle" :style="{'font-size':subTitleFontSize,'color':subTitleColor}" class="uni-section-header__content-sub">{{ subTitle }}</text> |
|||
</view> |
|||
|
|||
<view class="uni-section-header__slot-right"> |
|||
<slot name="right"></slot> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="uni-section-content" :style="{padding: _padding}"> |
|||
<slot /> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
|
|||
/** |
|||
* Section 标题栏 |
|||
* @description 标题栏 |
|||
* @property {String} type = [line|circle|square] 标题装饰类型 |
|||
* @value line 竖线 |
|||
* @value circle 圆形 |
|||
* @value square 正方形 |
|||
* @property {String} title 主标题 |
|||
* @property {String} titleFontSize 主标题字体大小 |
|||
* @property {String} titleColor 主标题字体颜色 |
|||
* @property {String} subTitle 副标题 |
|||
* @property {String} subTitleFontSize 副标题字体大小 |
|||
* @property {String} subTitleColor 副标题字体颜色 |
|||
* @property {String} padding 默认插槽 padding |
|||
*/ |
|||
|
|||
export default { |
|||
name: 'UniSection', |
|||
emits:['click'], |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
title: { |
|||
type: String, |
|||
required: true, |
|||
default: '' |
|||
}, |
|||
titleFontSize: { |
|||
type: String, |
|||
default: '14px' |
|||
}, |
|||
titleColor:{ |
|||
type: String, |
|||
default: '#333' |
|||
}, |
|||
subTitle: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
subTitleFontSize: { |
|||
type: String, |
|||
default: '12px' |
|||
}, |
|||
subTitleColor: { |
|||
type: String, |
|||
default: '#999' |
|||
}, |
|||
padding: { |
|||
type: [Boolean, String], |
|||
default: false |
|||
} |
|||
}, |
|||
computed:{ |
|||
_padding(){ |
|||
if(typeof this.padding === 'string'){ |
|||
return this.padding |
|||
} |
|||
|
|||
return this.padding?'10px':'' |
|||
} |
|||
}, |
|||
watch: { |
|||
title(newVal) { |
|||
if (uni.report && newVal !== '') { |
|||
uni.report('title', newVal) |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
onClick() { |
|||
this.$emit('click') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="scss" > |
|||
$uni-primary: #2979ff !default; |
|||
|
|||
.uni-section { |
|||
background-color: #fff; |
|||
.uni-section-header { |
|||
position: relative; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
align-items: center; |
|||
padding: 12px 10px; |
|||
font-weight: normal; |
|||
|
|||
&__decoration{ |
|||
margin-right: 6px; |
|||
background-color: $uni-primary; |
|||
&.line { |
|||
width: 4px; |
|||
height: 12px; |
|||
border-radius: 10px; |
|||
} |
|||
|
|||
&.circle { |
|||
width: 8px; |
|||
height: 8px; |
|||
border-top-right-radius: 50px; |
|||
border-top-left-radius: 50px; |
|||
border-bottom-left-radius: 50px; |
|||
border-bottom-right-radius: 50px; |
|||
} |
|||
|
|||
&.square { |
|||
width: 8px; |
|||
height: 8px; |
|||
} |
|||
} |
|||
|
|||
&__content { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
flex: 1; |
|||
color: #333; |
|||
|
|||
.distraction { |
|||
flex-direction: row; |
|||
align-items: center; |
|||
} |
|||
&-sub { |
|||
margin-top: 2px; |
|||
} |
|||
} |
|||
|
|||
&__slot-right{ |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.uni-section-content{ |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,26 @@ |
|||
// 应用全局配置
|
|||
module.exports = { |
|||
baseUrl: 'http://localhost:8080', |
|||
// baseUrl: 'http://localhost:8080',
|
|||
// 应用信息
|
|||
appInfo: { |
|||
// 应用名称
|
|||
name: "platform-app", |
|||
// 应用版本
|
|||
version: "1.0.0", |
|||
// 应用logo
|
|||
logo: "/static/logo.png", |
|||
// 官方网站
|
|||
site_url: "https://popsoft.tech", |
|||
// 政策协议
|
|||
agreements: [{ |
|||
title: "隐私政策", |
|||
url: "https://popsoft.tech/protocol.html" |
|||
}, |
|||
{ |
|||
title: "用户服务协议", |
|||
url: "https://popsoft.tech/protocol.html" |
|||
} |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
import Vue from 'vue' |
|||
import App from './App' |
|||
import store from './store' // store
|
|||
import plugins from './plugins' // plugins
|
|||
import './permission' // permission
|
|||
Vue.use(plugins) |
|||
|
|||
Vue.config.productionTip = false |
|||
Vue.prototype.$store = store |
|||
|
|||
App.mpType = 'app' |
|||
|
|||
const app = new Vue({ |
|||
...App |
|||
}) |
|||
|
|||
app.$mount() |
@ -0,0 +1,69 @@ |
|||
{ |
|||
"name" : "移动端", |
|||
"appid" : "__UNI__25A9D80", |
|||
"description" : "", |
|||
"versionName" : "1.1.0", |
|||
"versionCode" : "100", |
|||
"transformPx" : false, |
|||
"app-plus" : { |
|||
"usingComponents" : true, |
|||
"nvueCompiler" : "uni-app", |
|||
"splashscreen" : { |
|||
"alwaysShowBeforeRender" : true, |
|||
"waiting" : true, |
|||
"autoclose" : true, |
|||
"delay" : 0 |
|||
}, |
|||
"modules" : {}, |
|||
"distribute" : { |
|||
"android" : { |
|||
"permissions" : [ |
|||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
|||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
|||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
|||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
|||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
|||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
|||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
|||
"<uses-permission android:name=\"android.permission.CAMERA\"/>", |
|||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
|||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
|||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
|||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
|||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
|||
"<uses-feature android:name=\"android.hardware.camera\"/>", |
|||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" |
|||
] |
|||
}, |
|||
"ios" : {}, |
|||
"sdkConfigs" : {} |
|||
} |
|||
}, |
|||
"quickapp" : {}, |
|||
"mp-weixin" : { |
|||
"appid" : "wxccd7e2a0911b3397", |
|||
"setting" : { |
|||
"urlCheck" : false, |
|||
"es6" : false, |
|||
"minified" : true, |
|||
"postcss" : true |
|||
}, |
|||
"optimization" : { |
|||
"subPackages" : true |
|||
}, |
|||
"usingComponents" : true |
|||
}, |
|||
"vueVersion" : "2", |
|||
"h5" : { |
|||
"template" : "static/index.html", |
|||
"devServer" : { |
|||
"port" : 9090, |
|||
"https" : false |
|||
}, |
|||
"title" : "Platform-App", |
|||
"router" : { |
|||
"mode" : "hash", |
|||
"base" : "./" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,102 @@ |
|||
{ |
|||
"pages": [{ |
|||
"path": "pages/login", |
|||
"style": { |
|||
"navigationBarTitleText": "登录" |
|||
} |
|||
}, { |
|||
"path": "pages/register", |
|||
"style": { |
|||
"navigationBarTitleText": "注册" |
|||
} |
|||
}, { |
|||
"path": "pages/index", |
|||
"style": { |
|||
"navigationBarTitleText": "移动端框架", |
|||
"navigationStyle": "custom" |
|||
} |
|||
}, { |
|||
"path": "pages/work/index", |
|||
"style": { |
|||
"navigationBarTitleText": "工作台" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/index", |
|||
"style": { |
|||
"navigationBarTitleText": "我的" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/avatar/index", |
|||
"style": { |
|||
"navigationBarTitleText": "修改头像" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/info/index", |
|||
"style": { |
|||
"navigationBarTitleText": "个人信息" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/info/edit", |
|||
"style": { |
|||
"navigationBarTitleText": "编辑资料" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/pwd/index", |
|||
"style": { |
|||
"navigationBarTitleText": "修改密码" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/setting/index", |
|||
"style": { |
|||
"navigationBarTitleText": "应用设置" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/help/index", |
|||
"style": { |
|||
"navigationBarTitleText": "常见问题" |
|||
} |
|||
}, { |
|||
"path": "pages/mine/about/index", |
|||
"style": { |
|||
"navigationBarTitleText": "关于我们" |
|||
} |
|||
}, { |
|||
"path": "pages/common/webview/index", |
|||
"style": { |
|||
"navigationBarTitleText": "浏览网页" |
|||
} |
|||
}, { |
|||
"path": "pages/common/textview/index", |
|||
"style": { |
|||
"navigationBarTitleText": "浏览文本" |
|||
} |
|||
}], |
|||
"tabBar": { |
|||
"color": "#000000", |
|||
"selectedColor": "#000000", |
|||
"borderStyle": "white", |
|||
"backgroundColor": "#ffffff", |
|||
"list": [{ |
|||
"pagePath": "pages/index", |
|||
"iconPath": "static/images/tabbar/home.png", |
|||
"selectedIconPath": "static/images/tabbar/home_.png", |
|||
"text": "首页" |
|||
}, { |
|||
"pagePath": "pages/work/index", |
|||
"iconPath": "static/images/tabbar/work.png", |
|||
"selectedIconPath": "static/images/tabbar/work_.png", |
|||
"text": "工作台" |
|||
}, { |
|||
"pagePath": "pages/mine/index", |
|||
"iconPath": "static/images/tabbar/mine.png", |
|||
"selectedIconPath": "static/images/tabbar/mine_.png", |
|||
"text": "我的" |
|||
} |
|||
] |
|||
}, |
|||
"globalStyle": { |
|||
"navigationBarTextStyle": "black", |
|||
"navigationBarTitleText": "App", |
|||
"navigationBarBackgroundColor": "#FFFFFF" |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
<template> |
|||
<view> |
|||
<uni-card class="view-title" :title="title"> |
|||
<text class="uni-body view-content">{{ content }}</text> |
|||
</uni-card> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
title: '', |
|||
content: '' |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.title = options.title |
|||
this.content = options.content |
|||
uni.setNavigationBarTitle({ |
|||
title: options.title |
|||
}) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
page { |
|||
background-color: #ffffff; |
|||
} |
|||
|
|||
.view-title { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.view-content { |
|||
font-size: 26rpx; |
|||
padding: 12px 5px 0; |
|||
color: #333; |
|||
line-height: 24px; |
|||
font-weight: normal; |
|||
} |
|||
</style> |
@ -0,0 +1,34 @@ |
|||
<template> |
|||
<view v-if="params.url"> |
|||
<web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
params: {}, |
|||
webviewStyles: { |
|||
progress: { |
|||
color: "#FF3333" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
props: { |
|||
src: { |
|||
type: [String], |
|||
default: null |
|||
} |
|||
}, |
|||
onLoad(event) { |
|||
this.params = event |
|||
if (event.title) { |
|||
uni.setNavigationBarTitle({ |
|||
title: event.title |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,43 @@ |
|||
<template> |
|||
<view class="content"> |
|||
<image class="logo" src="@/static/logo.png"></image> |
|||
<view class="text-area"> |
|||
<text class="title">Hello ABC</text> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
onLoad: function() { |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.logo { |
|||
height: 200rpx; |
|||
width: 200rpx; |
|||
margin-top: 200rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
margin-bottom: 50rpx; |
|||
} |
|||
|
|||
.text-area { |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.title { |
|||
font-size: 36rpx; |
|||
color: #8f8f94; |
|||
} |
|||
</style> |
@ -0,0 +1,167 @@ |
|||
<template> |
|||
<view class="normal-login-container"> |
|||
<view class="logo-content align-center justify-center flex"> |
|||
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix"> |
|||
</image> |
|||
<text class="title">移动端登录</text> |
|||
</view> |
|||
<view class="login-form-content"> |
|||
<view class="input-item flex align-center"> |
|||
<view class="iconfont icon-user icon"></view> |
|||
<input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" /> |
|||
</view> |
|||
<view class="input-item flex align-center"> |
|||
<view class="iconfont icon-password icon"></view> |
|||
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" /> |
|||
</view> |
|||
<view class="action-btn"> |
|||
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button> |
|||
</view> |
|||
<view class="xieyi text-center"> |
|||
<text class="text-grey1">登录即代表同意</text> |
|||
<text @click="handleUserAgrement" class="text-blue">《用户协议》</text> |
|||
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
|
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
codeUrl: "", |
|||
captchaEnabled: true, |
|||
// 用户注册开关 |
|||
register: false, |
|||
globalConfig: getApp().globalData.config, |
|||
loginForm: { |
|||
username: "admin", |
|||
password: "12345678" |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
|
|||
}, |
|||
methods: { |
|||
// 用户注册 |
|||
handleUserRegister() { |
|||
this.$tab.redirectTo(`/pages/register`) |
|||
}, |
|||
// 隐私协议 |
|||
handlePrivacy() { |
|||
let site = this.globalConfig.appInfo.agreements[0] |
|||
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`) |
|||
}, |
|||
// 用户协议 |
|||
handleUserAgrement() { |
|||
let site = this.globalConfig.appInfo.agreements[1] |
|||
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`) |
|||
}, |
|||
|
|||
// 登录方法 |
|||
async handleLogin() { |
|||
if (this.loginForm.username === "") { |
|||
this.$modal.msgError("请输入您的账号") |
|||
} else if (this.loginForm.password === "") { |
|||
this.$modal.msgError("请输入您的密码") |
|||
}else { |
|||
this.$modal.loading("登录中,请耐心等待...") |
|||
this.pwdLogin() |
|||
} |
|||
}, |
|||
// 密码登录 |
|||
async pwdLogin() { |
|||
this.$store.dispatch('Login', this.loginForm).then((res) => { |
|||
this.$modal.closeLoading() |
|||
this.$tab.reLaunch('/pages/index') |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #ffffff; |
|||
} |
|||
|
|||
.normal-login-container { |
|||
width: 100%; |
|||
|
|||
.logo-content { |
|||
width: 100%; |
|||
font-size: 21px; |
|||
text-align: center; |
|||
padding-top: 15%; |
|||
|
|||
image { |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.title { |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
|
|||
.login-form-content { |
|||
text-align: center; |
|||
margin: 20px auto; |
|||
margin-top: 15%; |
|||
width: 80%; |
|||
|
|||
.input-item { |
|||
margin: 20px auto; |
|||
background-color: #f5f6f7; |
|||
height: 45px; |
|||
border-radius: 20px; |
|||
|
|||
.icon { |
|||
font-size: 38rpx; |
|||
margin-left: 10px; |
|||
color: #999; |
|||
} |
|||
|
|||
.input { |
|||
width: 100%; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
text-align: left; |
|||
padding-left: 15px; |
|||
} |
|||
|
|||
} |
|||
|
|||
.login-btn { |
|||
margin-top: 40px; |
|||
height: 45px; |
|||
} |
|||
|
|||
.reg { |
|||
margin-top: 15px; |
|||
} |
|||
|
|||
.xieyi { |
|||
color: #333; |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.login-code { |
|||
height: 38px; |
|||
float: right; |
|||
|
|||
.login-code-img { |
|||
height: 38px; |
|||
position: absolute; |
|||
margin-left: 10px; |
|||
width: 200rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
</style> |
@ -0,0 +1,75 @@ |
|||
<template> |
|||
<view class="about-container"> |
|||
<view class="header-section text-center"> |
|||
<image style="width: 150rpx;height: 150rpx;" src="/static/logo200.png" mode="widthFix"> |
|||
</image> |
|||
<uni-title type="h2" title="移动端"></uni-title> |
|||
</view> |
|||
|
|||
<view class="content-section"> |
|||
<view class="menu-list"> |
|||
<view class="list-cell list-cell-arrow"> |
|||
<view class="menu-item-box"> |
|||
<view>版本信息</view> |
|||
<view class="text-right">v{{version}}</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow"> |
|||
<view class="menu-item-box"> |
|||
<view>官方邮箱</view> |
|||
<view class="text-right">sealy321@126.com</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow"> |
|||
<view class="menu-item-box"> |
|||
<view>服务热线</view> |
|||
<view class="text-right">400-999-9999</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow"> |
|||
<view class="menu-item-box"> |
|||
<view>公司网站</view> |
|||
<view class="text-right"> |
|||
<uni-link :href="url" :text="url" showUnderLine="false"></uni-link> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="copyright"> |
|||
<view>Copyright © 2022 popsoft.tech All Rights Reserved.</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
url: getApp().globalData.config.appInfo.site_url, |
|||
version: getApp().globalData.config.appInfo.version |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #f8f8f8; |
|||
} |
|||
|
|||
.copyright { |
|||
margin-top: 50rpx; |
|||
text-align: center; |
|||
line-height: 60rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.header-section { |
|||
display: flex; |
|||
padding: 30rpx 0 0; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
</style> |
@ -0,0 +1,631 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<view class="page-body uni-content-info"> |
|||
<view class='cropper-content'> |
|||
<view v-if="isShowImg" class="uni-corpper" :style="'width:'+cropperInitW+'px;height:'+cropperInitH+'px;background:#000'"> |
|||
<view class="uni-corpper-content" :style="'width:'+cropperW+'px;height:'+cropperH+'px;left:'+cropperL+'px;top:'+cropperT+'px'"> |
|||
<image :src="imageSrc" :style="'width:'+cropperW+'px;height:'+cropperH+'px'"></image> |
|||
<view class="uni-corpper-crop-box" @touchstart.stop="contentStartMove" @touchmove.stop="contentMoveing" @touchend.stop="contentTouchEnd" |
|||
:style="'left:'+cutL+'px;top:'+cutT+'px;right:'+cutR+'px;bottom:'+cutB+'px'"> |
|||
<view class="uni-cropper-view-box"> |
|||
<view class="uni-cropper-dashed-h"></view> |
|||
<view class="uni-cropper-dashed-v"></view> |
|||
<view class="uni-cropper-line-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-line-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-line-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-line-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-point point-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-point point-tr" data-drag="topTight"></view> |
|||
<view class="uni-cropper-point point-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-point point-rb" data-drag="rightBottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-point point-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove" @touchend.stop="dragEnd"></view> |
|||
<view class="uni-cropper-point point-bl" data-drag="bottomLeft"></view> |
|||
<view class="uni-cropper-point point-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view> |
|||
<view class="uni-cropper-point point-lt" data-drag="leftTop"></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class='cropper-config'> |
|||
<button type="primary reverse" @click="getImage" style='margin-top: 30rpx;'> 选择头像 </button> |
|||
<button type="warn" @click="getImageInfo" style='margin-top: 30rpx;'> 提交 </button> |
|||
</view> |
|||
<canvas canvas-id="myCanvas" :style="'position:absolute;border: 1px solid red; width:'+imageW+'px;height:'+imageH+'px;top:-9999px;left:-9999px;'"></canvas> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import config from '@/config' |
|||
import store from "@/store" |
|||
import { uploadAvatar } from "@/api/system/user" |
|||
|
|||
const baseUrl = config.baseUrl |
|||
let sysInfo = uni.getSystemInfoSync() |
|||
let SCREEN_WIDTH = sysInfo.screenWidth |
|||
let PAGE_X, // 手按下的x位置 |
|||
PAGE_Y, // 手按下y的位置 |
|||
PR = sysInfo.pixelRatio, // dpi |
|||
T_PAGE_X, // 手移动的时候x的位置 |
|||
T_PAGE_Y, // 手移动的时候Y的位置 |
|||
CUT_L, // 初始化拖拽元素的left值 |
|||
CUT_T, // 初始化拖拽元素的top值 |
|||
CUT_R, // 初始化拖拽元素的 |
|||
CUT_B, // 初始化拖拽元素的 |
|||
CUT_W, // 初始化拖拽元素的宽度 |
|||
CUT_H, // 初始化拖拽元素的高度 |
|||
IMG_RATIO, // 图片比例 |
|||
IMG_REAL_W, // 图片实际的宽度 |
|||
IMG_REAL_H, // 图片实际的高度 |
|||
DRAFG_MOVE_RATIO = 1, //移动时候的比例, |
|||
INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度 |
|||
DRAW_IMAGE_W = sysInfo.screenWidth // 设置生成的图片宽度 |
|||
|
|||
export default { |
|||
/** |
|||
* 页面的初始数据 |
|||
*/ |
|||
data() { |
|||
return { |
|||
imageSrc: store.getters.avatar, |
|||
isShowImg: false, |
|||
// 初始化的宽高 |
|||
cropperInitW: SCREEN_WIDTH, |
|||
cropperInitH: SCREEN_WIDTH, |
|||
// 动态的宽高 |
|||
cropperW: SCREEN_WIDTH, |
|||
cropperH: SCREEN_WIDTH, |
|||
// 动态的left top值 |
|||
cropperL: 0, |
|||
cropperT: 0, |
|||
|
|||
transL: 0, |
|||
transT: 0, |
|||
|
|||
// 图片缩放值 |
|||
scaleP: 0, |
|||
imageW: 0, |
|||
imageH: 0, |
|||
|
|||
// 裁剪框 宽高 |
|||
cutL: 0, |
|||
cutT: 0, |
|||
cutB: SCREEN_WIDTH, |
|||
cutR: '100%', |
|||
qualityWidth: DRAW_IMAGE_W, |
|||
innerAspectRadio: DRAFG_MOVE_RATIO |
|||
} |
|||
}, |
|||
/** |
|||
* 生命周期函数--监听页面初次渲染完成 |
|||
*/ |
|||
onReady: function () { |
|||
this.loadImage() |
|||
}, |
|||
methods: { |
|||
setData: function (obj) { |
|||
let that = this |
|||
Object.keys(obj).forEach(function (key) { |
|||
that.$set(that.$data, key, obj[key]) |
|||
}) |
|||
}, |
|||
getImage: function () { |
|||
var _this = this |
|||
uni.chooseImage({ |
|||
success: function (res) { |
|||
_this.setData({ |
|||
imageSrc: res.tempFilePaths[0], |
|||
}) |
|||
_this.loadImage() |
|||
}, |
|||
}) |
|||
}, |
|||
loadImage: function () { |
|||
var _this = this |
|||
|
|||
uni.getImageInfo({ |
|||
src: _this.imageSrc, |
|||
success: function success(res) { |
|||
IMG_RATIO = 1 / 1 |
|||
if (IMG_RATIO >= 1) { |
|||
IMG_REAL_W = SCREEN_WIDTH |
|||
IMG_REAL_H = SCREEN_WIDTH / IMG_RATIO |
|||
} else { |
|||
IMG_REAL_W = SCREEN_WIDTH * IMG_RATIO |
|||
IMG_REAL_H = SCREEN_WIDTH |
|||
} |
|||
let minRange = IMG_REAL_W > IMG_REAL_H ? IMG_REAL_W : IMG_REAL_H |
|||
INIT_DRAG_POSITION = minRange > INIT_DRAG_POSITION ? INIT_DRAG_POSITION : minRange |
|||
// 根据图片的宽高显示不同的效果 保证图片可以正常显示 |
|||
if (IMG_RATIO >= 1) { |
|||
let cutT = Math.ceil((SCREEN_WIDTH / IMG_RATIO - (SCREEN_WIDTH / IMG_RATIO - INIT_DRAG_POSITION)) / 2) |
|||
let cutB = cutT |
|||
let cutL = Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH + INIT_DRAG_POSITION) / 2) |
|||
let cutR = cutL |
|||
_this.setData({ |
|||
cropperW: SCREEN_WIDTH, |
|||
cropperH: SCREEN_WIDTH / IMG_RATIO, |
|||
// 初始化left right |
|||
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2), |
|||
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH / IMG_RATIO) / 2), |
|||
cutL: cutL, |
|||
cutT: cutT, |
|||
cutR: cutR, |
|||
cutB: cutB, |
|||
// 图片缩放值 |
|||
imageW: IMG_REAL_W, |
|||
imageH: IMG_REAL_H, |
|||
scaleP: IMG_REAL_W / SCREEN_WIDTH, |
|||
qualityWidth: DRAW_IMAGE_W, |
|||
innerAspectRadio: IMG_RATIO |
|||
}) |
|||
} else { |
|||
let cutL = Math.ceil((SCREEN_WIDTH * IMG_RATIO - (SCREEN_WIDTH * IMG_RATIO)) / 2) |
|||
let cutR = cutL |
|||
let cutT = Math.ceil((SCREEN_WIDTH - INIT_DRAG_POSITION) / 2) |
|||
let cutB = cutT |
|||
_this.setData({ |
|||
cropperW: SCREEN_WIDTH * IMG_RATIO, |
|||
cropperH: SCREEN_WIDTH, |
|||
// 初始化left right |
|||
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH * IMG_RATIO) / 2), |
|||
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2), |
|||
|
|||
cutL: cutL, |
|||
cutT: cutT, |
|||
cutR: cutR, |
|||
cutB: cutB, |
|||
// 图片缩放值 |
|||
imageW: IMG_REAL_W, |
|||
imageH: IMG_REAL_H, |
|||
scaleP: IMG_REAL_W / SCREEN_WIDTH, |
|||
qualityWidth: DRAW_IMAGE_W, |
|||
innerAspectRadio: IMG_RATIO |
|||
}) |
|||
} |
|||
_this.setData({ |
|||
isShowImg: true |
|||
}) |
|||
uni.hideLoading() |
|||
} |
|||
}) |
|||
}, |
|||
// 拖动时候触发的touchStart事件 |
|||
contentStartMove(e) { |
|||
PAGE_X = e.touches[0].pageX |
|||
PAGE_Y = e.touches[0].pageY |
|||
}, |
|||
|
|||
// 拖动时候触发的touchMove事件 |
|||
contentMoveing(e) { |
|||
var _this = this |
|||
var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO |
|||
var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO |
|||
// 左移 |
|||
if (dragLengthX > 0) { |
|||
if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL |
|||
} else { |
|||
if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR |
|||
} |
|||
|
|||
if (dragLengthY > 0) { |
|||
if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT |
|||
} else { |
|||
if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB |
|||
} |
|||
this.setData({ |
|||
cutL: this.cutL - dragLengthX, |
|||
cutT: this.cutT - dragLengthY, |
|||
cutR: this.cutR + dragLengthX, |
|||
cutB: this.cutB + dragLengthY |
|||
}) |
|||
|
|||
PAGE_X = e.touches[0].pageX |
|||
PAGE_Y = e.touches[0].pageY |
|||
}, |
|||
|
|||
contentTouchEnd() { |
|||
|
|||
}, |
|||
|
|||
// 获取图片 |
|||
getImageInfo() { |
|||
var _this = this |
|||
uni.showLoading({ |
|||
title: '图片生成中...', |
|||
}) |
|||
// 将图片写入画布 |
|||
const ctx = uni.createCanvasContext('myCanvas') |
|||
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H) |
|||
ctx.draw(true, () => { |
|||
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio) |
|||
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W |
|||
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H |
|||
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W |
|||
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H |
|||
uni.canvasToTempFilePath({ |
|||
x: canvasL, |
|||
y: canvasT, |
|||
width: canvasW, |
|||
height: canvasH, |
|||
destWidth: canvasW, |
|||
destHeight: canvasH, |
|||
quality: 0.5, |
|||
canvasId: 'myCanvas', |
|||
success: function (res) { |
|||
uni.hideLoading() |
|||
let data = {name: 'avatarfile', filePath: res.tempFilePath} |
|||
uploadAvatar(data).then(response => { |
|||
store.commit('SET_AVATAR', baseUrl + response.imgUrl) |
|||
uni.showToast({ title: "修改成功", icon: 'success' }) |
|||
uni.navigateBack() |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}, |
|||
// 设置大小的时候触发的touchStart事件 |
|||
dragStart(e) { |
|||
T_PAGE_X = e.touches[0].pageX |
|||
T_PAGE_Y = e.touches[0].pageY |
|||
CUT_L = this.cutL |
|||
CUT_R = this.cutR |
|||
CUT_B = this.cutB |
|||
CUT_T = this.cutT |
|||
}, |
|||
|
|||
// 设置大小的时候触发的touchMove事件 |
|||
dragMove(e) { |
|||
var _this = this |
|||
var dragType = e.target.dataset.drag |
|||
switch (dragType) { |
|||
case 'right': |
|||
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO |
|||
if (CUT_R + dragLength < 0) dragLength = -CUT_R |
|||
this.setData({ |
|||
cutR: CUT_R + dragLength |
|||
}) |
|||
break |
|||
case 'left': |
|||
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO |
|||
if (CUT_L - dragLength < 0) dragLength = CUT_L |
|||
if ((CUT_L - dragLength) > (this.cropperW - this.cutR)) dragLength = CUT_L - (this.cropperW - this.cutR) |
|||
this.setData({ |
|||
cutL: CUT_L - dragLength |
|||
}) |
|||
break |
|||
case 'top': |
|||
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO |
|||
if (CUT_T - dragLength < 0) dragLength = CUT_T |
|||
if ((CUT_T - dragLength) > (this.cropperH - this.cutB)) dragLength = CUT_T - (this.cropperH - this.cutB) |
|||
this.setData({ |
|||
cutT: CUT_T - dragLength |
|||
}) |
|||
break |
|||
case 'bottom': |
|||
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO |
|||
if (CUT_B + dragLength < 0) dragLength = -CUT_B |
|||
this.setData({ |
|||
cutB: CUT_B + dragLength |
|||
}) |
|||
break |
|||
case 'rightBottom': |
|||
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO |
|||
var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO |
|||
|
|||
if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B |
|||
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R |
|||
let cutB = CUT_B + dragLengthY |
|||
let cutR = CUT_R + dragLengthX |
|||
|
|||
this.setData({ |
|||
cutB: cutB, |
|||
cutR: cutR |
|||
}) |
|||
break |
|||
default: |
|||
break |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/* pages/uni-cropper/index.wxss */ |
|||
|
|||
.uni-content-info { |
|||
/* position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
display: block; |
|||
align-items: center; |
|||
flex-direction: column; */ |
|||
} |
|||
|
|||
.cropper-config { |
|||
padding: 20rpx 40rpx; |
|||
} |
|||
|
|||
.cropper-content { |
|||
min-height: 750rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.uni-corpper { |
|||
position: relative; |
|||
overflow: hidden; |
|||
-webkit-user-select: none; |
|||
-moz-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
-webkit-tap-highlight-color: transparent; |
|||
-webkit-touch-callout: none; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.uni-corpper-content { |
|||
position: relative; |
|||
} |
|||
|
|||
.uni-corpper-content image { |
|||
display: block; |
|||
width: 100%; |
|||
min-width: 0 !important; |
|||
max-width: none !important; |
|||
height: 100%; |
|||
min-height: 0 !important; |
|||
max-height: none !important; |
|||
image-orientation: 0deg !important; |
|||
margin: 0 auto; |
|||
} |
|||
/* 移动图片效果 */ |
|||
|
|||
.uni-cropper-drag-box { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
cursor: move; |
|||
background: rgba(0, 0, 0, 0.6); |
|||
z-index: 1; |
|||
} |
|||
/* 内部的信息 */ |
|||
|
|||
.uni-corpper-crop-box { |
|||
position: absolute; |
|||
background: rgba(255, 255, 255, 0.3); |
|||
z-index: 2; |
|||
} |
|||
|
|||
.uni-corpper-crop-box .uni-cropper-view-box { |
|||
position: relative; |
|||
display: block; |
|||
width: 100%; |
|||
height: 100%; |
|||
overflow: visible; |
|||
outline: 1rpx solid #69f; |
|||
outline-color: rgba(102, 153, 255, .75) |
|||
} |
|||
/* 横向虚线 */ |
|||
|
|||
.uni-cropper-dashed-h { |
|||
position: absolute; |
|||
top: 33.33333333%; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 33.33333333%; |
|||
border-top: 1rpx dashed rgba(255, 255, 255, 0.5); |
|||
border-bottom: 1rpx dashed rgba(255, 255, 255, 0.5); |
|||
} |
|||
/* 纵向虚线 */ |
|||
|
|||
.uni-cropper-dashed-v { |
|||
position: absolute; |
|||
left: 33.33333333%; |
|||
top: 0; |
|||
width: 33.33333333%; |
|||
height: 100%; |
|||
border-left: 1rpx dashed rgba(255, 255, 255, 0.5); |
|||
border-right: 1rpx dashed rgba(255, 255, 255, 0.5); |
|||
} |
|||
/* 四个方向的线 为了之后的拖动事件*/ |
|||
|
|||
.uni-cropper-line-t { |
|||
position: absolute; |
|||
display: block; |
|||
width: 100%; |
|||
background-color: #69f; |
|||
top: 0; |
|||
left: 0; |
|||
height: 1rpx; |
|||
opacity: 0.1; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.uni-cropper-line-t::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 50%; |
|||
right: 0rpx; |
|||
width: 100%; |
|||
-webkit-transform: translate3d(0, -50%, 0); |
|||
transform: translate3d(0, -50%, 0); |
|||
bottom: 0; |
|||
height: 41rpx; |
|||
background: transparent; |
|||
z-index: 11; |
|||
} |
|||
|
|||
.uni-cropper-line-r { |
|||
position: absolute; |
|||
display: block; |
|||
background-color: #69f; |
|||
top: 0; |
|||
right: 0rpx; |
|||
width: 1rpx; |
|||
opacity: 0.1; |
|||
height: 100%; |
|||
cursor: e-resize; |
|||
} |
|||
|
|||
.uni-cropper-line-r::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 50%; |
|||
width: 41rpx; |
|||
-webkit-transform: translate3d(-50%, 0, 0); |
|||
transform: translate3d(-50%, 0, 0); |
|||
bottom: 0; |
|||
height: 100%; |
|||
background: transparent; |
|||
z-index: 11; |
|||
} |
|||
|
|||
.uni-cropper-line-b { |
|||
position: absolute; |
|||
display: block; |
|||
width: 100%; |
|||
background-color: #69f; |
|||
bottom: 0; |
|||
left: 0; |
|||
height: 1rpx; |
|||
opacity: 0.1; |
|||
cursor: s-resize; |
|||
} |
|||
|
|||
.uni-cropper-line-b::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 50%; |
|||
right: 0rpx; |
|||
width: 100%; |
|||
-webkit-transform: translate3d(0, -50%, 0); |
|||
transform: translate3d(0, -50%, 0); |
|||
bottom: 0; |
|||
height: 41rpx; |
|||
background: transparent; |
|||
z-index: 11; |
|||
} |
|||
|
|||
.uni-cropper-line-l { |
|||
position: absolute; |
|||
display: block; |
|||
background-color: #69f; |
|||
top: 0; |
|||
left: 0; |
|||
width: 1rpx; |
|||
opacity: 0.1; |
|||
height: 100%; |
|||
cursor: w-resize; |
|||
} |
|||
|
|||
.uni-cropper-line-l::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 50%; |
|||
width: 41rpx; |
|||
-webkit-transform: translate3d(-50%, 0, 0); |
|||
transform: translate3d(-50%, 0, 0); |
|||
bottom: 0; |
|||
height: 100%; |
|||
background: transparent; |
|||
z-index: 11; |
|||
} |
|||
|
|||
.uni-cropper-point { |
|||
width: 5rpx; |
|||
height: 5rpx; |
|||
background-color: #69f; |
|||
opacity: .75; |
|||
position: absolute; |
|||
z-index: 3; |
|||
} |
|||
|
|||
.point-t { |
|||
top: -3rpx; |
|||
left: 50%; |
|||
margin-left: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.point-tr { |
|||
top: -3rpx; |
|||
left: 100%; |
|||
margin-left: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.point-r { |
|||
top: 50%; |
|||
left: 100%; |
|||
margin-left: -3rpx; |
|||
margin-top: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.point-rb { |
|||
left: 100%; |
|||
top: 100%; |
|||
-webkit-transform: translate3d(-50%, -50%, 0); |
|||
transform: translate3d(-50%, -50%, 0); |
|||
cursor: n-resize; |
|||
width: 36rpx; |
|||
height: 36rpx; |
|||
background-color: #69f; |
|||
position: absolute; |
|||
z-index: 1112; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.point-b { |
|||
left: 50%; |
|||
top: 100%; |
|||
margin-left: -3rpx; |
|||
margin-top: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.point-bl { |
|||
left: 0%; |
|||
top: 100%; |
|||
margin-left: -3rpx; |
|||
margin-top: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.point-l { |
|||
left: 0%; |
|||
top: 50%; |
|||
margin-left: -3rpx; |
|||
margin-top: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
|
|||
.point-lt { |
|||
left: 0%; |
|||
top: 0%; |
|||
margin-left: -3rpx; |
|||
margin-top: -3rpx; |
|||
cursor: n-resize; |
|||
} |
|||
/* 裁剪框预览内容 */ |
|||
|
|||
.uni-cropper-viewer { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 100%; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.uni-cropper-viewer image { |
|||
position: absolute; |
|||
z-index: 2; |
|||
} |
|||
</style> |
@ -0,0 +1,112 @@ |
|||
<template> |
|||
<view class="help-container"> |
|||
<view v-for="(item, findex) in list" :key="findex" :title="item.title" class="list-title"> |
|||
<view class="text-title"> |
|||
<view :class="item.icon"></view>{{ item.title }} |
|||
</view> |
|||
<view class="childList"> |
|||
<view v-for="(child, zindex) in item.childList" :key="zindex" class="question" hover-class="hover" |
|||
@click="handleText(child)"> |
|||
<view class="text-item">{{ child.title }}</view> |
|||
<view class="line" v-if="zindex !== item.childList.length - 1"></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
list: [{ |
|||
icon: 'iconfont icon-github', |
|||
title: '问题', |
|||
childList: [{ |
|||
title: '开源吗?', |
|||
content: '开源' |
|||
}, { |
|||
title: '可以商用吗?', |
|||
content: '可以' |
|||
}, { |
|||
title: '官网地址多少?', |
|||
content: 'http://popsoft.tech' |
|||
}, { |
|||
title: '文档地址多少?', |
|||
content: 'http://popsoft.tech' |
|||
}] |
|||
}, |
|||
{ |
|||
icon: 'iconfont icon-help', |
|||
title: '其他问题', |
|||
childList: [{ |
|||
title: '如何退出登录?', |
|||
content: '请点击[我的] - [应用设置] - [退出登录]即可退出登录', |
|||
}, { |
|||
title: '如何修改用户头像?', |
|||
content: '请点击[我的] - [选择头像] - [点击提交]即可更换用户头像', |
|||
}, { |
|||
title: '如何修改登录密码?', |
|||
content: '请点击[我的] - [应用设置] - [修改密码]即可修改登录密码', |
|||
}] |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
methods: { |
|||
handleText(item) { |
|||
this.$tab.navigateTo(`/pages/common/textview/index?title=${item.title}&content=${item.content}`) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
page { |
|||
background-color: #f8f8f8; |
|||
} |
|||
|
|||
.help-container { |
|||
margin-bottom: 100rpx; |
|||
padding: 30rpx; |
|||
} |
|||
|
|||
.list-title { |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.childList { |
|||
background: #ffffff; |
|||
box-shadow: 0px 0px 10rpx rgba(193, 193, 193, 0.2); |
|||
border-radius: 16rpx; |
|||
margin-top: 10rpx; |
|||
} |
|||
|
|||
.line { |
|||
width: 100%; |
|||
height: 1rpx; |
|||
background-color: #F5F5F5; |
|||
} |
|||
|
|||
.text-title { |
|||
color: #303133; |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
margin-left: 10rpx; |
|||
|
|||
.iconfont { |
|||
font-size: 16px; |
|||
margin-right: 10rpx; |
|||
} |
|||
} |
|||
|
|||
.text-item { |
|||
font-size: 28rpx; |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.question { |
|||
color: #606266; |
|||
font-size: 28rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,195 @@ |
|||
<template> |
|||
<view class="mine-container" :style="{height: `${windowHeight}px`}"> |
|||
<!--顶部个人信息栏--> |
|||
<view class="header-section"> |
|||
<view class="flex padding justify-between"> |
|||
<view class="flex align-center"> |
|||
<view v-if="!avatar" class="cu-avatar xl round bg-white"> |
|||
<view class="iconfont icon-people text-gray icon"></view> |
|||
</view> |
|||
<image v-if="avatar" @click="handleToAvatar" :src="avatar" class="cu-avatar xl round" mode="widthFix"> |
|||
</image> |
|||
<view v-if="!name" @click="handleToLogin" class="login-tip"> |
|||
点击登录 |
|||
</view> |
|||
<view v-if="name" @click="handleToInfo" class="user-info"> |
|||
<view class="u_title"> |
|||
{{ name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
<view class="content-section"> |
|||
<view class="mine-actions grid col-4 text-center"> |
|||
<view class="action-item" @click="handleJiaoLiuQun"> |
|||
<view class="iconfont icon-friendfill text-pink icon"></view> |
|||
<text class="text">交流群</text> |
|||
</view> |
|||
<view class="action-item" @click="handleBuilding"> |
|||
<view class="iconfont icon-service text-blue icon"></view> |
|||
<text class="text">在线客服</text> |
|||
</view> |
|||
<view class="action-item" @click="handleBuilding"> |
|||
<view class="iconfont icon-community text-mauve icon"></view> |
|||
<text class="text">反馈社区</text> |
|||
</view> |
|||
<view class="action-item" @click="handleBuilding"> |
|||
<view class="iconfont icon-dianzan text-green icon"></view> |
|||
<text class="text">点赞我们</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="menu-list"> |
|||
<view class="list-cell list-cell-arrow" @click="handleToEditInfo"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-user menu-icon"></view> |
|||
<view>编辑资料</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow" @click="handleHelp"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-help menu-icon"></view> |
|||
<view>常见问题</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow" @click="handleAbout"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-aixin menu-icon"></view> |
|||
<view>关于我们</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow" @click="handleToSetting"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-setting menu-icon"></view> |
|||
<view>应用设置</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import storage from '@/utils/storage' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
name: this.$store.state.user.name, |
|||
version: getApp().globalData.config.appInfo.version |
|||
} |
|||
}, |
|||
computed: { |
|||
avatar() { |
|||
return this.$store.state.user.avatar |
|||
}, |
|||
windowHeight() { |
|||
return uni.getSystemInfoSync().windowHeight - 50 |
|||
} |
|||
}, |
|||
methods: { |
|||
handleToInfo() { |
|||
this.$tab.navigateTo('/pages/mine/info/index') |
|||
}, |
|||
handleToEditInfo() { |
|||
this.$tab.navigateTo('/pages/mine/info/edit') |
|||
}, |
|||
handleToSetting() { |
|||
this.$tab.navigateTo('/pages/mine/setting/index') |
|||
}, |
|||
handleToLogin() { |
|||
this.$tab.reLaunch('/pages/login') |
|||
}, |
|||
handleToAvatar() { |
|||
this.$tab.navigateTo('/pages/mine/avatar/index') |
|||
}, |
|||
handleLogout() { |
|||
this.$modal.confirm('确定注销并退出系统吗?').then(() => { |
|||
this.$store.dispatch('LogOut').then(() => { |
|||
this.$tab.reLaunch('/pages/index') |
|||
}) |
|||
}) |
|||
}, |
|||
handleHelp() { |
|||
this.$tab.navigateTo('/pages/mine/help/index') |
|||
}, |
|||
handleAbout() { |
|||
this.$tab.navigateTo('/pages/mine/about/index') |
|||
}, |
|||
handleJiaoLiuQun() { |
|||
this.$modal.showToast('模块建设中~') |
|||
}, |
|||
handleBuilding() { |
|||
this.$modal.showToast('模块建设中~') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #f5f6f7; |
|||
} |
|||
|
|||
.mine-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
|
|||
|
|||
.header-section { |
|||
padding: 15px 15px 45px 15px; |
|||
background-color: #3c96f3; |
|||
color: white; |
|||
|
|||
.login-tip { |
|||
font-size: 18px; |
|||
margin-left: 10px; |
|||
} |
|||
|
|||
.cu-avatar { |
|||
border: 2px solid #eaeaea; |
|||
|
|||
.icon { |
|||
font-size: 40px; |
|||
} |
|||
} |
|||
|
|||
.user-info { |
|||
margin-left: 15px; |
|||
|
|||
.u_title { |
|||
font-size: 18px; |
|||
line-height: 30px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.content-section { |
|||
position: relative; |
|||
top: -50px; |
|||
|
|||
.mine-actions { |
|||
margin: 15px 15px; |
|||
padding: 20px 0px; |
|||
border-radius: 8px; |
|||
background-color: white; |
|||
|
|||
.action-item { |
|||
.icon { |
|||
font-size: 28px; |
|||
} |
|||
|
|||
.text { |
|||
display: block; |
|||
font-size: 13px; |
|||
margin: 8px 0px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,127 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<view class="example"> |
|||
<uni-forms ref="form" :model="user" labelWidth="80px"> |
|||
<uni-forms-item label="用户昵称" name="nickName"> |
|||
<uni-easyinput v-model="user.nickName" placeholder="请输入昵称" /> |
|||
</uni-forms-item> |
|||
<uni-forms-item label="手机号码" name="phonenumber"> |
|||
<uni-easyinput v-model="user.phonenumber" placeholder="请输入手机号码" /> |
|||
</uni-forms-item> |
|||
<uni-forms-item label="邮箱" name="email"> |
|||
<uni-easyinput v-model="user.email" placeholder="请输入邮箱" /> |
|||
</uni-forms-item> |
|||
<uni-forms-item label="性别" name="sex" required> |
|||
<uni-data-checkbox v-model="user.sex" :localdata="sexs" /> |
|||
</uni-forms-item> |
|||
</uni-forms> |
|||
<button type="primary" @click="submit">提交</button> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getUserProfile } from "@/api/system/user" |
|||
import { updateUserProfile } from "@/api/system/user" |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
user: { |
|||
nickName: "", |
|||
phonenumber: "", |
|||
email: "", |
|||
sex: "" |
|||
}, |
|||
sexs: [{ |
|||
text: '男', |
|||
value: "0" |
|||
}, { |
|||
text: '女', |
|||
value: "1" |
|||
}], |
|||
rules: { |
|||
nickName: { |
|||
rules: [{ |
|||
required: true, |
|||
errorMessage: '用户昵称不能为空' |
|||
}] |
|||
}, |
|||
phonenumber: { |
|||
rules: [{ |
|||
required: true, |
|||
errorMessage: '手机号码不能为空' |
|||
}, { |
|||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, |
|||
errorMessage: '请输入正确的手机号码' |
|||
}] |
|||
}, |
|||
email: { |
|||
rules: [{ |
|||
required: true, |
|||
errorMessage: '邮箱地址不能为空' |
|||
}, { |
|||
format: 'email', |
|||
errorMessage: '请输入正确的邮箱地址' |
|||
}] |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
onLoad() { |
|||
this.getUser() |
|||
}, |
|||
onReady() { |
|||
this.$refs.form.setRules(this.rules) |
|||
}, |
|||
methods: { |
|||
getUser() { |
|||
getUserProfile().then(response => { |
|||
this.user = response.data |
|||
}) |
|||
}, |
|||
submit(ref) { |
|||
this.$refs.form.validate().then(res => { |
|||
updateUserProfile(this.user).then(response => { |
|||
this.$modal.msgSuccess("修改成功") |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #ffffff; |
|||
} |
|||
|
|||
.example { |
|||
padding: 15px; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.segmented-control { |
|||
margin-bottom: 15px; |
|||
} |
|||
|
|||
.button-group { |
|||
margin-top: 15px; |
|||
display: flex; |
|||
justify-content: space-around; |
|||
} |
|||
|
|||
.form-item { |
|||
display: flex; |
|||
align-items: center; |
|||
flex: 1; |
|||
} |
|||
|
|||
.button { |
|||
display: flex; |
|||
align-items: center; |
|||
height: 35px; |
|||
line-height: 35px; |
|||
margin-left: 10px; |
|||
} |
|||
</style> |
@ -0,0 +1,44 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<uni-list> |
|||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'person-filled'}" title="昵称" :rightText="user.nickName" /> |
|||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'phone-filled'}" title="手机号码" :rightText="user.phonenumber" /> |
|||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'email-filled'}" title="邮箱" :rightText="user.email" /> |
|||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'auth-filled'}" title="岗位" :rightText="postGroup" /> |
|||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'staff-filled'}" title="角色" :rightText="roleGroup" /> |
|||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'calendar-filled'}" title="创建日期" :rightText="user.createTime" /> |
|||
</uni-list> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getUserProfile } from "@/api/system/user" |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
user: {}, |
|||
roleGroup: "", |
|||
postGroup: "" |
|||
} |
|||
}, |
|||
onLoad() { |
|||
this.getUser() |
|||
}, |
|||
methods: { |
|||
getUser() { |
|||
getUserProfile().then(response => { |
|||
this.user = response.data |
|||
this.roleGroup = response.roleGroup |
|||
this.postGroup = response.postGroup |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #ffffff; |
|||
} |
|||
</style> |
@ -0,0 +1,85 @@ |
|||
<template> |
|||
<view class="pwd-retrieve-container"> |
|||
<uni-forms ref="form" :value="user" labelWidth="80px"> |
|||
<uni-forms-item name="oldPassword" label="旧密码"> |
|||
<uni-easyinput type="password" v-model="user.oldPassword" placeholder="请输入旧密码" /> |
|||
</uni-forms-item> |
|||
<uni-forms-item name="newPassword" label="新密码"> |
|||
<uni-easyinput type="password" v-model="user.newPassword" placeholder="请输入新密码" /> |
|||
</uni-forms-item> |
|||
<uni-forms-item name="confirmPassword" label="确认密码"> |
|||
<uni-easyinput type="password" v-model="user.confirmPassword" placeholder="请确认新密码" /> |
|||
</uni-forms-item> |
|||
<button type="primary" @click="submit">提交</button> |
|||
</uni-forms> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { updateUserPwd } from "@/api/system/user" |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
user: { |
|||
oldPassword: undefined, |
|||
newPassword: undefined, |
|||
confirmPassword: undefined |
|||
}, |
|||
rules: { |
|||
oldPassword: { |
|||
rules: [{ |
|||
required: true, |
|||
errorMessage: '旧密码不能为空' |
|||
}] |
|||
}, |
|||
newPassword: { |
|||
rules: [{ |
|||
required: true, |
|||
errorMessage: '新密码不能为空', |
|||
}, |
|||
{ |
|||
minLength: 6, |
|||
maxLength: 20, |
|||
errorMessage: '长度在 6 到 20 个字符' |
|||
} |
|||
] |
|||
}, |
|||
confirmPassword: { |
|||
rules: [{ |
|||
required: true, |
|||
errorMessage: '确认密码不能为空' |
|||
}, { |
|||
validateFunction: (rule, value, data) => data.newPassword === value, |
|||
errorMessage: '两次输入的密码不一致' |
|||
} |
|||
] |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
onReady() { |
|||
this.$refs.form.setRules(this.rules) |
|||
}, |
|||
methods: { |
|||
submit() { |
|||
this.$refs.form.validate().then(res => { |
|||
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => { |
|||
this.$modal.msgSuccess("修改成功") |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #ffffff; |
|||
} |
|||
|
|||
.pwd-retrieve-container { |
|||
padding-top: 36rpx; |
|||
padding: 15px; |
|||
} |
|||
</style> |
@ -0,0 +1,78 @@ |
|||
<template> |
|||
<view class="setting-container" :style="{height: `${windowHeight}px`}"> |
|||
<view class="menu-list"> |
|||
<view class="list-cell list-cell-arrow" @click="handleToPwd"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-password menu-icon"></view> |
|||
<view>修改密码</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow" @click="handleToUpgrade"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-refresh menu-icon"></view> |
|||
<view>检查更新</view> |
|||
</view> |
|||
</view> |
|||
<view class="list-cell list-cell-arrow" @click="handleCleanTmp"> |
|||
<view class="menu-item-box"> |
|||
<view class="iconfont icon-clean menu-icon"></view> |
|||
<view>清理缓存</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="cu-list menu"> |
|||
<view class="cu-item item-box"> |
|||
<view class="content text-center" @click="handleLogout"> |
|||
<text class="text-black">退出登录</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
windowHeight: uni.getSystemInfoSync().windowHeight |
|||
} |
|||
}, |
|||
methods: { |
|||
handleToPwd() { |
|||
this.$tab.navigateTo('/pages/mine/pwd/index') |
|||
}, |
|||
handleToUpgrade() { |
|||
this.$modal.showToast('模块建设中~') |
|||
}, |
|||
handleCleanTmp() { |
|||
this.$modal.showToast('模块建设中~') |
|||
}, |
|||
handleLogout() { |
|||
this.$modal.confirm('确定注销并退出系统吗?').then(() => { |
|||
this.$store.dispatch('LogOut').then(() => { |
|||
this.$tab.reLaunch('/pages/index') |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.page { |
|||
background-color: #f8f8f8; |
|||
} |
|||
|
|||
.item-box { |
|||
background-color: #FFFFFF; |
|||
margin: 30rpx; |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 10rpx; |
|||
border-radius: 8rpx; |
|||
color: #303133; |
|||
font-size: 32rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,196 @@ |
|||
<template> |
|||
<view class="normal-login-container"> |
|||
<view class="logo-content align-center justify-center flex"> |
|||
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix"> |
|||
</image> |
|||
<text class="title">移动端注册</text> |
|||
</view> |
|||
<view class="login-form-content"> |
|||
<view class="input-item flex align-center"> |
|||
<view class="iconfont icon-user icon"></view> |
|||
<input v-model="registerForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" /> |
|||
</view> |
|||
<view class="input-item flex align-center"> |
|||
<view class="iconfont icon-password icon"></view> |
|||
<input v-model="registerForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" /> |
|||
</view> |
|||
<view class="input-item flex align-center"> |
|||
<view class="iconfont icon-password icon"></view> |
|||
<input v-model="registerForm.confirmPassword" type="password" class="input" placeholder="请输入重复密码" maxlength="20" /> |
|||
</view> |
|||
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled"> |
|||
<view class="iconfont icon-code icon"></view> |
|||
<input v-model="registerForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" /> |
|||
<view class="login-code"> |
|||
<image :src="codeUrl" @click="getCode" class="login-code-img"></image> |
|||
</view> |
|||
</view> |
|||
<view class="action-btn"> |
|||
<button @click="handleRegister()" class="register-btn cu-btn block bg-blue lg round">注册</button> |
|||
</view> |
|||
</view> |
|||
<view class="xieyi text-center"> |
|||
<text @click="handleUserLogin" class="text-blue">使用已有账号登录</text> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getCodeImg, register } from '@/api/login' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
codeUrl: "", |
|||
captchaEnabled: true, |
|||
globalConfig: getApp().globalData.config, |
|||
registerForm: { |
|||
username: "", |
|||
password: "", |
|||
confirmPassword: "", |
|||
code: "", |
|||
uuid: '' |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
this.getCode() |
|||
}, |
|||
methods: { |
|||
// 用户登录 |
|||
handleUserLogin() { |
|||
this.$tab.navigateTo(`/pages/login`) |
|||
}, |
|||
// 获取图形验证码 |
|||
getCode() { |
|||
getCodeImg().then(res => { |
|||
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled |
|||
if (this.captchaEnabled) { |
|||
this.codeUrl = 'data:image/gif;base64,' + res.img |
|||
this.registerForm.uuid = res.uuid |
|||
} |
|||
}) |
|||
}, |
|||
// 注册方法 |
|||
async handleRegister() { |
|||
if (this.registerForm.username === "") { |
|||
this.$modal.msgError("请输入您的账号") |
|||
} else if (this.registerForm.password === "") { |
|||
this.$modal.msgError("请输入您的密码") |
|||
} else if (this.registerForm.confirmPassword === "") { |
|||
this.$modal.msgError("请再次输入您的密码") |
|||
} else if (this.registerForm.password !== this.registerForm.confirmPassword) { |
|||
this.$modal.msgError("两次输入的密码不一致") |
|||
} else if (this.registerForm.code === "" && this.captchaEnabled) { |
|||
this.$modal.msgError("请输入验证码") |
|||
} else { |
|||
this.$modal.loading("注册中,请耐心等待...") |
|||
this.register() |
|||
} |
|||
}, |
|||
// 用户注册 |
|||
async register() { |
|||
register(this.registerForm).then(res => { |
|||
this.$modal.closeLoading() |
|||
uni.showModal({ |
|||
title: "系统提示", |
|||
content: "恭喜你,您的账号 " + this.registerForm.username + " 注册成功!", |
|||
success: function (res) { |
|||
if (res.confirm) { |
|||
uni.redirectTo({ url: `/pages/login` }); |
|||
} |
|||
} |
|||
}) |
|||
}).catch(() => { |
|||
if (this.captchaEnabled) { |
|||
this.getCode() |
|||
} |
|||
}) |
|||
}, |
|||
// 注册成功后,处理函数 |
|||
registerSuccess(result) { |
|||
// 设置用户信息 |
|||
this.$store.dispatch('GetInfo').then(res => { |
|||
this.$tab.reLaunch('/pages/index') |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background-color: #ffffff; |
|||
} |
|||
|
|||
.normal-login-container { |
|||
width: 100%; |
|||
|
|||
.logo-content { |
|||
width: 100%; |
|||
font-size: 21px; |
|||
text-align: center; |
|||
padding-top: 15%; |
|||
|
|||
image { |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.title { |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
|
|||
.login-form-content { |
|||
text-align: center; |
|||
margin: 20px auto; |
|||
margin-top: 15%; |
|||
width: 80%; |
|||
|
|||
.input-item { |
|||
margin: 20px auto; |
|||
background-color: #f5f6f7; |
|||
height: 45px; |
|||
border-radius: 20px; |
|||
|
|||
.icon { |
|||
font-size: 38rpx; |
|||
margin-left: 10px; |
|||
color: #999; |
|||
} |
|||
|
|||
.input { |
|||
width: 100%; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
text-align: left; |
|||
padding-left: 15px; |
|||
} |
|||
|
|||
} |
|||
|
|||
.register-btn { |
|||
margin-top: 40px; |
|||
height: 45px; |
|||
} |
|||
|
|||
.xieyi { |
|||
color: #333; |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.login-code { |
|||
height: 38px; |
|||
float: right; |
|||
|
|||
.login-code-img { |
|||
height: 38px; |
|||
position: absolute; |
|||
margin-left: 10px; |
|||
width: 200rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
</style> |
@ -0,0 +1,181 @@ |
|||
<template> |
|||
<view class="work-container"> |
|||
<!-- 轮播图 --> |
|||
<uni-swiper-dot class="uni-swiper-dot-box" :info="data" :current="current" field="content"> |
|||
<swiper class="swiper-box" :current="swiperDotIndex" @change="changeSwiper"> |
|||
<swiper-item v-for="(item, index) in data" :key="index"> |
|||
<view class="swiper-item" @click="clickBannerItem(item)"> |
|||
<image :src="item.image" mode="aspectFill" :draggable="false" /> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</uni-swiper-dot> |
|||
|
|||
<!-- 宫格组件 --> |
|||
<uni-section title="系统管理" type="line"></uni-section> |
|||
<view class="grid-body"> |
|||
<uni-grid :column="4" :showBorder="false" @change="changeGrid"> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="person-filled" size="30"></uni-icons> |
|||
<text class="text">用户管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="staff-filled" size="30"></uni-icons> |
|||
<text class="text">角色管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="color" size="30"></uni-icons> |
|||
<text class="text">菜单管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="settings-filled" size="30"></uni-icons> |
|||
<text class="text">部门管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="heart-filled" size="30"></uni-icons> |
|||
<text class="text">岗位管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="bars" size="30"></uni-icons> |
|||
<text class="text">字典管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="gear-filled" size="30"></uni-icons> |
|||
<text class="text">参数设置</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="chat-filled" size="30"></uni-icons> |
|||
<text class="text">通知公告</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
<uni-grid-item> |
|||
<view class="grid-item-box"> |
|||
<uni-icons type="wallet-filled" size="30"></uni-icons> |
|||
<text class="text">日志管理</text> |
|||
</view> |
|||
</uni-grid-item> |
|||
</uni-grid> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
current: 0, |
|||
swiperDotIndex: 0, |
|||
data: [ |
|||
{ |
|||
image: '/static/images/banner/banner02.jpg' |
|||
}, |
|||
{ |
|||
image: '/static/images/banner/banner03.jpg' |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
methods: { |
|||
clickBannerItem(item) { |
|||
console.info(item) |
|||
}, |
|||
changeSwiper(e) { |
|||
this.current = e.detail.current |
|||
}, |
|||
changeGrid(e) { |
|||
this.$modal.showToast('模块建设中~') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
/* #ifndef APP-NVUE */ |
|||
page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
box-sizing: border-box; |
|||
background-color: #fff; |
|||
min-height: 100%; |
|||
height: auto; |
|||
} |
|||
|
|||
view { |
|||
font-size: 14px; |
|||
line-height: inherit; |
|||
} |
|||
|
|||
/* #endif */ |
|||
|
|||
.text { |
|||
text-align: center; |
|||
font-size: 26rpx; |
|||
margin-top: 10rpx; |
|||
} |
|||
|
|||
.grid-item-box { |
|||
flex: 1; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 15px 0; |
|||
} |
|||
|
|||
.uni-margin-wrap { |
|||
width: 690rpx; |
|||
width: 100%; |
|||
; |
|||
} |
|||
|
|||
.swiper { |
|||
height: 300rpx; |
|||
} |
|||
|
|||
.swiper-box { |
|||
height: 150px; |
|||
} |
|||
|
|||
.swiper-item { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
color: #fff; |
|||
height: 300rpx; |
|||
line-height: 300rpx; |
|||
} |
|||
|
|||
@media screen and (min-width: 500px) { |
|||
.uni-swiper-dot-box { |
|||
width: 400px; |
|||
/* #ifndef APP-NVUE */ |
|||
margin: 0 auto; |
|||
/* #endif */ |
|||
margin-top: 8px; |
|||
} |
|||
|
|||
.image { |
|||
width: 100%; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,39 @@ |
|||
import { getToken } from '@/utils/auth' |
|||
|
|||
// 登录页面
|
|||
const loginPage = "/pages/login" |
|||
|
|||
// 页面白名单
|
|||
const whiteList = [ |
|||
'/pages/login', '/pages/register', '/pages/common/webview/index' |
|||
] |
|||
|
|||
// 检查地址白名单
|
|||
function checkWhite(url) { |
|||
const path = url.split('?')[0] |
|||
return whiteList.indexOf(path) !== -1 |
|||
} |
|||
|
|||
// 页面跳转验证拦截器
|
|||
let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"] |
|||
list.forEach(item => { |
|||
uni.addInterceptor(item, { |
|||
invoke(to) { |
|||
if (getToken()) { |
|||
if (to.url === loginPage) { |
|||
uni.reLaunch({ url: "/" }) |
|||
} |
|||
return true |
|||
} else { |
|||
if (checkWhite(to.url)) { |
|||
return true |
|||
} |
|||
uni.reLaunch({ url: loginPage }) |
|||
return false |
|||
} |
|||
}, |
|||
fail(err) { |
|||
console.log(err) |
|||
} |
|||
}) |
|||
}) |
@ -0,0 +1,60 @@ |
|||
import store from '@/store' |
|||
|
|||
function authPermission(permission) { |
|||
const all_permission = "*:*:*" |
|||
const permissions = store.getters && store.getters.permissions |
|||
if (permission && permission.length > 0) { |
|||
return permissions.some(v => { |
|||
return all_permission === v || v === permission |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
function authRole(role) { |
|||
const super_admin = "admin" |
|||
const roles = store.getters && store.getters.roles |
|||
if (role && role.length > 0) { |
|||
return roles.some(v => { |
|||
return super_admin === v || v === role |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
export default { |
|||
// 验证用户是否具备某权限
|
|||
hasPermi(permission) { |
|||
return authPermission(permission) |
|||
}, |
|||
// 验证用户是否含有指定权限,只需包含其中一个
|
|||
hasPermiOr(permissions) { |
|||
return permissions.some(item => { |
|||
return authPermission(item) |
|||
}) |
|||
}, |
|||
// 验证用户是否含有指定权限,必须全部拥有
|
|||
hasPermiAnd(permissions) { |
|||
return permissions.every(item => { |
|||
return authPermission(item) |
|||
}) |
|||
}, |
|||
// 验证用户是否具备某角色
|
|||
hasRole(role) { |
|||
return authRole(role) |
|||
}, |
|||
// 验证用户是否含有指定角色,只需包含其中一个
|
|||
hasRoleOr(roles) { |
|||
return roles.some(item => { |
|||
return authRole(item) |
|||
}) |
|||
}, |
|||
// 验证用户是否含有指定角色,必须全部拥有
|
|||
hasRoleAnd(roles) { |
|||
return roles.every(item => { |
|||
return authRole(item) |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
import tab from './tab' |
|||
import auth from './auth' |
|||
import modal from './modal' |
|||
|
|||
export default { |
|||
install(Vue) { |
|||
// 页签操作
|
|||
Vue.prototype.$tab = tab |
|||
// 认证对象
|
|||
Vue.prototype.$auth = auth |
|||
// 模态框对象
|
|||
Vue.prototype.$modal = modal |
|||
} |
|||
} |
@ -0,0 +1,74 @@ |
|||
export default { |
|||
// 消息提示
|
|||
msg(content) { |
|||
uni.showToast({ |
|||
title: content, |
|||
icon: 'none' |
|||
}) |
|||
}, |
|||
// 错误消息
|
|||
msgError(content) { |
|||
uni.showToast({ |
|||
title: content, |
|||
icon: 'error' |
|||
}) |
|||
}, |
|||
// 成功消息
|
|||
msgSuccess(content) { |
|||
uni.showToast({ |
|||
title: content, |
|||
icon: 'success' |
|||
}) |
|||
}, |
|||
// 隐藏消息
|
|||
hideMsg(content) { |
|||
uni.hideToast() |
|||
}, |
|||
// 弹出提示
|
|||
alert(content, title) { |
|||
uni.showModal({ |
|||
title: title || '系统提示', |
|||
content: content, |
|||
showCancel: false |
|||
}) |
|||
}, |
|||
// 确认窗体
|
|||
confirm(content, title) { |
|||
return new Promise((resolve, reject) => { |
|||
uni.showModal({ |
|||
title: title || '系统提示', |
|||
content: content, |
|||
cancelText: '取消', |
|||
confirmText: '确定', |
|||
success: function(res) { |
|||
if (res.confirm) { |
|||
resolve(res.confirm) |
|||
} |
|||
} |
|||
}) |
|||
}) |
|||
}, |
|||
// 提示信息
|
|||
showToast(option) { |
|||
if (typeof option === "object") { |
|||
uni.showToast(option) |
|||
} else { |
|||
uni.showToast({ |
|||
title: option, |
|||
icon: "none", |
|||
duration: 2500 |
|||
}) |
|||
} |
|||
}, |
|||
// 打开遮罩层
|
|||
loading(content) { |
|||
uni.showLoading({ |
|||
title: content, |
|||
icon: 'none' |
|||
}) |
|||
}, |
|||
// 关闭遮罩层
|
|||
closeLoading() { |
|||
uni.hideLoading() |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
export default { |
|||
// 关闭所有页面,打开到应用内的某个页面
|
|||
reLaunch(url) { |
|||
return uni.reLaunch({ |
|||
url: url |
|||
}) |
|||
}, |
|||
// 跳转到tabBar页面,并关闭其他所有非tabBar页面
|
|||
switchTab(url) { |
|||
return uni.switchTab({ |
|||
url: url |
|||
}) |
|||
}, |
|||
// 关闭当前页面,跳转到应用内的某个页面
|
|||
redirectTo(url) { |
|||
return uni.redirectTo({ |
|||
url: url |
|||
}) |
|||
}, |
|||
// 保留当前页面,跳转到应用内的某个页面
|
|||
navigateTo(url) { |
|||
return uni.navigateTo({ |
|||
url: url |
|||
}) |
|||
}, |
|||
// 关闭当前页面,返回上一页面或多级页面
|
|||
navigateBack() { |
|||
return uni.navigateBack() |
|||
} |
|||
} |
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,90 @@ |
|||
@font-face { |
|||
font-family: "iconfont"; |
|||
src: url('@/static/font/iconfont.ttf') format('truetype'); |
|||
} |
|||
|
|||
.iconfont { |
|||
font-family: "iconfont" !important; |
|||
font-size: 16px; |
|||
display: inline-block; |
|||
font-style: normal; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
.icon-user:before { |
|||
content: "\e7ae"; |
|||
} |
|||
|
|||
.icon-password:before { |
|||
content: "\e8b2"; |
|||
} |
|||
|
|||
.icon-code:before { |
|||
content: "\e699"; |
|||
} |
|||
|
|||
.icon-setting:before { |
|||
content: "\e6cc"; |
|||
} |
|||
|
|||
.icon-share:before { |
|||
content: "\e739"; |
|||
} |
|||
|
|||
.icon-edit:before { |
|||
content: "\e60c"; |
|||
} |
|||
|
|||
.icon-version:before { |
|||
content: "\e63f"; |
|||
} |
|||
|
|||
.icon-service:before { |
|||
content: "\e6ff"; |
|||
} |
|||
|
|||
.icon-friendfill:before { |
|||
content: "\e726"; |
|||
} |
|||
|
|||
.icon-community:before { |
|||
content: "\e741"; |
|||
} |
|||
|
|||
.icon-people:before { |
|||
content: "\e736"; |
|||
} |
|||
|
|||
.icon-dianzan:before { |
|||
content: "\ec7f"; |
|||
} |
|||
|
|||
.icon-right:before { |
|||
content: "\e7eb"; |
|||
} |
|||
|
|||
.icon-logout:before { |
|||
content: "\e61d"; |
|||
} |
|||
|
|||
.icon-help:before { |
|||
content: "\e616"; |
|||
} |
|||
|
|||
.icon-github:before { |
|||
content: "\e628"; |
|||
} |
|||
|
|||
.icon-aixin:before { |
|||
content: "\e601"; |
|||
} |
|||
|
|||
.icon-clean:before { |
|||
content: "\e607"; |
|||
} |
|||
|
|||
.icon-refresh:before { |
|||
content: "\e604"; |
|||
} |
|||
|
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.9 KiB |
@ -0,0 +1,20 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
|||
<meta name="renderer" content="webkit"> |
|||
<title><%= htmlWebpackPlugin.options.title %></title> |
|||
<link rel="shortcut icon" type="image/x-icon" href="<%= BASE_URL %>static/favicon.ico"> |
|||
<script> |
|||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)')) |
|||
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />') |
|||
</script> |
|||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" /> |
|||
</head> |
|||
<body> |
|||
<noscript> |
|||
<strong>本站点必须要开启JavaScript才能运行.</strong> |
|||
</noscript> |
|||
<div id="app"></div> |
|||
</html> |
After Width: | Height: | Size: 975 B |
After Width: | Height: | Size: 3.7 KiB |