This commit is contained in:
梁显优 2022-11-19 16:00:50 +08:00
commit e2f4e82323
37 changed files with 4149 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/out/
*.iml

0
.idea/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="z-im:jar">
<output-path>$PROJECT_DIR$/out/artifacts/z_im_jar</output-path>
<root id="archive" name="HaoSportProject.jar">
<element id="module-output" name="HaoSportProject" />
</root>
</artifact>
</component>

8
.idea/compiler.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
</annotationProcessing>
</component>
</project>

12
.idea/libraries/lib.xml Normal file
View File

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="lib">
<CLASSES>
<root url="jar://D:/wk/libs/z-core.jar!/" />
<root url="jar://D:/wk/libs/redkale.jar!/" />
<root url="jar://D:/wk/libs/mysql-connector-java-8.0.18.jar!/" />
<root url="jar://D:/wk/libs/lombok-1.18.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/ZimPlatfProject.iml" filepath="$PROJECT_DIR$/ZimPlatfProject.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

138
.idea/workspace.xml Normal file
View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ArtifactsWorkspaceSettings">
<artifacts-to-build>
<artifact name="z-im:jar" />
</artifacts-to-build>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="22feb48f-4d0d-4bc6-b200-bfc5d6a85040" name="变更" comment=".">
<change beforePath="$PROJECT_DIR$/conf/_conf/_config.properties" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/conf/_conf/application.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/conf/application.xml" beforeDir="false" afterPath="$PROJECT_DIR$/conf/application.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/base/BaseEntity.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/base/BaseService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/com/zchd/base/BaseService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/base/info/SwearWordService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/com/zchd/base/info/SwearWordService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/base/util/FileKit.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/com/zchd/base/util/FileKit.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/base/util/PSubBean.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/base/util/QiniuUtils.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/zim/ImAccountService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/com/zchd/zim/ImAccountService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/com/zchd/zim/ImChatWebSocket.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/com/zchd/zim/ImChatWebSocket.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2Hk3V13tpwIzPYUs3ORxKq2da8n" />
<component name="ProjectLevelVcsManager">
<OptionsSetting value="false" id="Update" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"last_opened_file_path": "D:/wk/libs/lombok-1.18.20.jar",
"project.structure.last.edited": "Problems",
"project.structure.proportion": "0.15",
"project.structure.side.proportion": "0.3137931",
"settings.editor.selected.configurable": "preferences.pluginManager"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="E:\wk-hao\HaoSportProject\out\artifacts\zim" />
<recent name="E:\wk-hao\HaoSportProject\out\artifacts\libs" />
</key>
</component>
<component name="RunManager">
<configuration name="Unnamed" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="org.redkale.boot.Application" />
<module name="ZimPlatfProject" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="22feb48f-4d0d-4bc6-b200-bfc5d6a85040" name="变更" comment="" />
<created>1668815275339</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1668815275339</updated>
</task>
<task id="LOCAL-00001" summary=".">
<created>1668826741516</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1668826741516</updated>
</task>
<option name="localTasksCounter" value="2" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State>
<option name="FILTERS">
<map>
<entry key="user">
<value>
<list>
<option value="*" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
<option name="RECENT_FILTERS">
<map>
<entry key="User">
<value>
<list>
<RecentGroup>
<option name="FILTER_VALUES">
<option value="*" />
</option>
</RecentGroup>
</list>
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="." />
<option name="LAST_COMMIT_MESSAGE" value="." />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<breakpoint enabled="true" type="java-exception">
<properties class="java.nio.channels.AsynchronousCloseException" package="java.nio.channels" />
<option name="timeStamp" value="2" />
</breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>

44
conf/application.xml Normal file
View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<application port="2091" name="ZIM_LOCAL">
<zhubs>
<zhub name="zhub" addr="47.111.150.118:6066" groupid="zim-zhub" auth="zchd@123456"/>
</zhubs>
<resources>
<properties load="config.properties"></properties>
<listener value="com.zdemo.ZhubListener"/>
<listener value="com.zchd.base.AppListener"/>
<source name="int_cache" value="com.zdemo.cachex.MyRedisCacheSource">
<node addr="47.111.150.118" password="*Zhong9307!" port="6064" db="0"/>
</source>
<source name="str_cache" value="com.zdemo.cachex.MyRedisCacheSource">
<node addr="47.111.150.118" password="*Zhong9307!" port="6064" db="0"/>
</source>
<source name="long_cache" value="com.zdemo.cachex.MyRedisCacheSource">
<node addr="47.111.150.118" password="*Zhong9307!" port="6064" db="0"/>
</source>
</resources>
<server protocol="HTTP" port="8091" maxbody="2m">
<request>
<remoteaddr value="request.headers.X-Real-IP"/>
</request>
<rest autoload="true" base="com.zchd.base.BaseServlet" path="/"/>
<response>
<addheader name="X-Node" value="system.property.APP_NODE"/>
<addheader name="Access-Control-Allow-Methods" value="*"/>
<addheader name="Access-Control-Max-Age" value="3600"/>
<addheader name="Access-Control-Allow-Headers" value="*"/>
<addheader name="Access-Control-Allow-Credentials" value="true"/>
<addheader name="Access-Control-Allow-Origin" value="*"/>
</response>
<services autoload="true"/>
<servlets path="/"/>
</server>
</application>

2
conf/config.properties Normal file
View File

@ -0,0 +1,2 @@
# swear
# swearbasepath=/opt/swear

16
conf/logging.properties Normal file
View File

@ -0,0 +1,16 @@
handlers=java.util.logging.ConsoleHandler
############################################################
.level=FINEST
java.level=INFO
javax.level=INFO
com.sun.level=INFO
sun.level=INFO
jdk.level=INFO
java.util.logging.FileHandler.level=FINE
#10M
java.util.logging.FileHandler.limit=10485760
java.util.logging.FileHandler.count=10000
java.util.logging.FileHandler.encoding=UTF-8
java.util.logging.FileHandler.pattern=${APP_HOME}/logs-%m/log-%d.log
java.util.logging.FileHandler.append=true
java.util.logging.ConsoleHandler.level=FINEST

15
conf/persistence.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="z_im">
<shared-cache-mode>ALL</shared-cache-mode>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://47.111.150.118:6063/z_im"/>
<property name="javax.persistence.jdbc.user" value="vsport"/>
<property name="javax.persistence.jdbc.password" value="*Zhong123098!"/>
</properties>
</persistence-unit>
</persistence>

View File

@ -0,0 +1,32 @@
package com.zchd.base;
import com.zchd.base.util.QueueTask;
import org.redkale.boot.Application;
import org.redkale.boot.ApplicationListener;
import javax.annotation.Resource;
import java.io.File;
import java.util.logging.Logger;
/**
* 服务监听
*
* @author: liangxy.
*/
public class AppListener implements ApplicationListener {
private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
@Resource(name = "APP_HOME")
protected File APP_HOME;
@Override
public void preStart(Application application) {
}
@Override
public void preShutdown(Application application) {
QueueTask.destroys();
}
}

View File

@ -0,0 +1,24 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.zchd.base;
import org.redkale.convert.json.JsonConvert;
import org.redkale.service.RetResult;
import java.io.Serializable;
/**
* @author zhangjx
*/
public abstract class BaseBean implements Serializable {
public final static RetResult RESULT_SUCCESS = RetResult.success();
@Override
public String toString() {
return JsonConvert.root().convertTo(this);
}
}

View File

@ -0,0 +1,52 @@
package com.zchd.base;
import com.zdemo.cachex.MyRedisCacheSource;
import com.zdemo.zhub.ZHubClient;
import org.redkale.convert.json.JsonConvert;
import org.redkale.service.AbstractService;
import org.redkale.service.RetResult;
import org.redkale.source.DataSource;
import org.redkale.util.Sheet;
import javax.annotation.Resource;
import java.io.File;
import java.util.List;
import java.util.logging.Logger;
public class BaseService extends AbstractService {
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
protected final static RetResult RET_SUCCESS = RetResult.success();
protected final static RetResult RET_EMPTY_SHEET = RetResult.success(new Sheet<>(0, List.of()));
protected final static RetResult RET_EMPTY_LIST = RetResult.success(List.of());
protected final JsonConvert convert = JsonConvert.root();
@Resource(name = "zhub")
protected ZHubClient zhub;
@Resource(name = "int_cache")
protected MyRedisCacheSource<Integer> intCache;
@Resource(name = "long_cache")
protected MyRedisCacheSource<Long> longCache;
@Resource(name = "str_cache")
protected MyRedisCacheSource<String> strCache;
@Resource(name = "z_im")
protected DataSource zimSource;
@Resource(name = "APP_HOME")
protected File APP_HOME;
@Resource(name = "APP_NAME")
protected String APP_NAME = "";
protected RetResult retError(String info) {
return new RetResult<>(100, info);
}
protected RetResult retError(int code, String info) {
return new RetResult<>(code, info);
}
}

View File

@ -0,0 +1,49 @@
package com.zchd.base;
import org.redkale.convert.json.JsonConvert;
import org.redkale.net.http.HttpRequest;
import org.redkale.net.http.HttpResponse;
import org.redkale.net.http.HttpServlet;
import org.redkale.service.RetResult;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class BaseServlet extends HttpServlet {
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
@Resource(name = "APP_NAME")
protected String APP_NAME = "";
@Resource
protected JsonConvert convert;
@Override
protected void preExecute(HttpRequest request, HttpResponse response) throws IOException {
super.preExecute(request, response);
}
@Override
protected void authenticate(HttpRequest request, HttpResponse response) throws IOException {
response.nextEvent();
}
@Override
public void execute(HttpRequest request, HttpResponse response) throws IOException {
try {
super.execute(request, response);
} catch (Exception e) {
logger.log(Level.WARNING, "", e);
RetResult result = RetResult.success();
result.setRetcode(100);
result.setRetinfo("操作失败");
response.finishJson(result);
return;
}
}
}

View File

@ -0,0 +1,477 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.zchd.base;
import com.zchd.base.util.Utils;
import org.redkale.service.RetLabel;
import org.redkale.service.RetResult;
import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* @author zhangjx
*/
@SuppressWarnings("unchecked")
public abstract class RetCodes {
//2000_0001 - 2999_9999 预留给 Redkale的扩展包redkalex使用
//3000_0001 - 7999_9999 为平台系统使用
//8000_0001 - 9999_9999 为OSS系统使用
//------------------------------------- 通用模块 -----------------------------------------
@RetLabel("参数无效")
public static final int RET_PARAMS_ILLEGAL = 30010001;
@RetLabel("无上传文件")
public static final int RET_UPLOAD_NOFILE = 30010002;
@RetLabel("上传文件过大")
public static final int RET_UPLOAD_FILETOOBIG = 30010003;
@RetLabel("上传文件不是图片")
public static final int RET_UPLOAD_NOTIMAGE = 30010004;
@RetLabel("文件写入失败")
public static final int RET_FILE_WRITE_ERROR = 30010005;
@RetLabel("系统内部异常")
public static final int RET_INNER_ILLEGAL = 30010006;
@RetLabel("调用远程接口异常")
public static final int RET_REMOTE_ILLEGAL = 30010007;
@RetLabel("调用远程接口超时")
public static final int RET_REMOTE_TIMEOUT = 30010008;
@RetLabel("重复操作")
public static final int RET_REPEAT_ILLEGAL = 30010009;
@RetLabel("操作失败")
public static final int RET_UNKNOWN_ERROR = 30010010;
@RetLabel("操作失败,包含敏感词")
public static final int RET_SWEAR_ERROR = 30010011;
@RetLabel("网络繁忙,请稍后再试")
public static final int RET_FREQUENCY_ERROR = 30010012;
@RetLabel("不在白名单内")
public static final int RET_FREQUENCY_NOT_WHITE = 30010013;
@RetLabel("您操作速度太快,请稍后再试")
public static final int RET_FREQUENCY_SPEED_FAST = 30010014;
@RetLabel("权限不足")
public static final int RET_AUTH_ILLEGAL = 30010015;
@RetLabel("请求参数长度超出限制2M")
public static final int RET_REQUEST_PARAM_LENGTH_OUT = 30010016;
@RetLabel("手速太快,请稍后再试")
public static final int RET_REQUEST_UNIQUE = 30010017;
//------------------------------------- 用户模块 -----------------------------------------
@RetLabel("未登陆")
public static final int RET_USER_UNLOGIN = 30020001;
@RetLabel("用户登录失败")
public static final int RET_USER_LOGIN_FAIL = 30020002;
@RetLabel("用户被禁用")
public static final int RET_USER_FREEZED = 30020005;
@RetLabel("用户权限不够")
public static final int RET_USER_AUTH_ILLEGAL = 30020006;
@RetLabel("用户不存在")
public static final int RET_USER_NOTEXISTS = 30020007;
@RetLabel("用户状态异常")
public static final int RET_USER_STATUS_ILLEGAL = 30020008;
@RetLabel("用户注册参数无效")
public static final int RET_USER_SIGNUP_ILLEGAL = 30020009;
@RetLabel("用户性别参数无效")
public static final int RET_USER_GENDER_ILLEGAL = 30020010;
@RetLabel("用户名无效")
public static final int RET_USER_USERNAME_ILLEGAL = 30020011;
@RetLabel("用户账号无效")
public static final int RET_USER_ACCOUNT_ILLEGAL = 30020012;
@RetLabel("手机号已存在")
public static final int RET_USER_ACCOUNT_EXISTS = 30020013;
@RetLabel("手机号码无效")
public static final int RET_USER_MOBILE_ILLEGAL = 30020014;
@RetLabel("手机号码已存在")
public static final int RET_USER_MOBILE_EXISTS = 30020015;
@RetLabel("手机验证码发送过于频繁")
public static final int RET_USER_MOBILE_SMSFREQUENT = 30020016;
@RetLabel("邮箱地址无效")
public static final int RET_USER_EMAIL_ILLEGAL = 30020017;
@RetLabel("邮箱地址已存在")
public static final int RET_USER_EMAIL_EXISTS = 30020018;
@RetLabel("微信绑定号无效")
public static final int RET_USER_WXID_ILLEGAL = 30020019;
@RetLabel("微信绑定号已存在")
public static final int RET_USER_WXID_EXISTS = 30020020;
@RetLabel("绑定微信号失败")
public static final int RET_USER_WXID_BIND_FAIL = 30020021;
@RetLabel("QQ绑定号无效")
public static final int RET_USER_QQID_ILLEGAL = 30020022;
@RetLabel("QQ绑定号已存在")
public static final int RET_USER_QQID_EXISTS = 30020023;
@RetLabel("绑定QQ号失败")
public static final int RET_USER_QQID_BIND_FAIL = 30020024;
@RetLabel("获取绑定QQ信息失败")
public static final int RET_USER_QQID_INFO_FAIL = 30020025;
@RetLabel("验证码无效")
public static final int RET_USER_RANDCODE_ILLEGAL = 30020026; //邮件或者短信验证码
@RetLabel("验证码已过期")
public static final int RET_USER_RANDCODE_EXPIRED = 30020027; //邮件或者短信验证码
@RetLabel("验证码错误或失效")
public static final int RET_USER_CAPTCHA_ILLEGAL = 30020028; //图片验证码
@RetLabel("用户类型无效")
public static final int RET_USER_TYPE_ILLEGAL = 30020029;
@RetLabel("账号已在其他设备登录")
public static final int RET_USER_LOGIN_ILLEGAL = 30020030;
@RetLabel("今日已签到")
public static final int RET_QUESTS_DUTY_EXISTS = 30020031;
@RetLabel("父账号不存在")
public static final int RET_USER_PARENT_ILLEGAL = 30020032;
@RetLabel("手机号码所在运营商不存在")
public static final int RET_USER_MOBILE_NONET = 30020033;
@RetLabel("用户等级不够")
public static final int RET_USER_LEVEL_ILLEGAL = 30020036;
@RetLabel("银行密码错误")
public static final int RET_USER_BANKPWD_ILLEGAL = 30020037;
@RetLabel("用户已绑定在其他设备上了")
public static final int RET_USER_APPTOKEN_BINDED = 30020039;
@RetLabel("短信发送失败")
public static final int RET_SMS_SEND_ERROR = 30020041;
@RetLabel("同一设备注册次数达到上限")
public static final int RET_USER_REG_APPSAME_LIMIT = 30020042;
@RetLabel("您的账号已被限制登录")
public static final int RET_USER_LOGINORREG_LIMIT = 30020043;
@RetLabel("城信APP绑定号已存在")
public static final int RET_USER_CXID_EXISTS = 30020044;
@RetLabel("禁止循环代理")
public static final int RET_USER_AGENCY_REPEAT = 30020045;
@RetLabel("没有可提取的奖励")
public static final int RET_USER_PROFIT_MONEY_ILLEGAL = 30020046;
@RetLabel("收款资料不全")
public static final int RET_USER_PROFIT_CARD_ILLEGAL = 30020047;
@RetLabel("用户生日无效")
public static final int RET_USER_BIRTHDAY_ILLEGAL = 30020048;
@RetLabel("举报参数无效")
public static final int RET_USER_REPORT_ILLEGAL = 30020049;
@RetLabel("邀请码无效")
public static final int RET_USER_INVITE_ILLEGAL = 30020050;
@RetLabel("此用户昵称已存在")
public static final int RET_USERNAME_EXISTS = 30020051;
@RetLabel("昵称不可用")
public static final int RET_USERNAME_ILLEGAL = 30020052;
@RetLabel("用户地址无效")
public static final int RET_ADDR_ILLEGAL = 30020053;
@RetLabel("用户头像无效")
public static final int RET_FACE_ILLEGAL = 30020054;
@RetLabel("用户封面无效")
public static final int RET_USERCOVER_ILLEGAL = 30020055;
@RetLabel("第三方用户信息获取失败")
public static final int RET_JTOKEN_ILLEGAL = 30020056;
@RetLabel("用户年龄参数无效")
public static final int RET_USER_AGE_ILLEGAL = 30020057;
@RetLabel("接口调用Token无效")
public static final int RET_USERCREATE_TOKEN_ILLEGAL = 30020057;
@RetLabel("抽奖券不足")
public static final int RET_LUCK_DRAW_TICKET_ILLEGAL = 30020058;
@RetLabel("您的账号已被冻结")
public static final int RET_USER_STATUS_FROZEN = 30020059;
@RetLabel("您已被禁言")
public static final int RET_USER_STATUS_FREEZE = 30020060;
@RetLabel("邀请码已达到使用上限")
public static final int RET_USER_INVITE_UPPER = 30020061;
@RetLabel("用户未激活")
public static final int RET_USER_STATUS_PENDING = 30020062;
@RetLabel("用户未进行身份认证")
public static final int RET_USER_IDENTITY_NOT_PROVED = 30020063;
@RetLabel("解绑后没有其它登录方式,不允许解绑")
public static final int RET_USER_NOT_UNBIND = 30020065;
@RetLabel("已是推广员,请勿重复申请")
public static final int RET_USER_ALREADY_PROMOTER = 30020066;
@RetLabel("请充值会员")
public static final int RET_USER_VIP_ERROR = 30020067;
@RetLabel("已举报过,请勿重复举报")
public static final int RET_USER_ALREADY_REPORT = 30020068;
@RetLabel("Apple Id绑定号已存在")
public static final int RET_USER_APPLEID_EXISTS = 30020069;
@RetLabel("您的登录状态已过期")
public static final int RET_USER_LOGIN_EXPIRE = 30020070;
@RetLabel("手机号已绑定其它第三方账号")
public static final int RET_USER_MOBILE_BINDED = 30020071;
@RetLabel("名片背景图无效")
public static final int RET_USER_CARD_IMG_ERROR = 30020072;
@RetLabel("未查询到用户收货地址")
public static final int RET_USER_ADDRESS_NOT_EXISTS = 30020073;
@RetLabel("7天内只能修改1次请稍后再试~")
public static final int RET_USER_CHANGE_NAME_COUNT = 30020074;
@RetLabel("开通VIP解锁多张封面特权")
public static final int RET_USER_BATCH_COVER_NO_VIP = 30020075;
@RetLabel("手机号已绑定其它账号")
public static final int RET_MOBILE_BIND_OTHER = 30020076;
@RetLabel("手机号已注册V运动账号是否确认绑定")
public static final int RET_MOBILE_EXISTS_CONFIRM = 30020077;
@RetLabel("您的账号长时间未登录,为保证账号安全,请重新输入账号密码")
public static final int RET_ACCOUNT_LOGIN_EXPIRE = 30020078;
//----------------------------------------头衔认证-------------------------------------------------------------------
@RetLabel("已申请或拥有此头衔")
public static final int RET_IDENTITY_ALREADY_APPLY = 40100001;
@RetLabel("不满足申请条件")
public static final int RET_IDENTITY_CANNOT_APPLY = 40100002;
@RetLabel("佩戴头衔超出限制")
public static final int RET_IDENTITY_WEAR_ERROR = 40100003;
@RetLabel("自定义头衔昵称为空")
public static final int RET_IDENTITY_CUSTOM_NAME_ERROR = 40100004;
@RetLabel("自定义头衔名称不能与提供的名称重复")
public static final int RET_IDENTITY_NAME_REPEAT = 40100005;
@RetLabel("未获得头衔")
public static final int RET_IDENTITY_NOT_HAD = 40100006;
@RetLabel("文件大小超过限制3M")
public static final int RET_IDENTITY_FILE_LENGTH_OUT = 40100007;
@RetLabel("文件类型超出限制jpeg,jpg,png")
public static final int RET_IDENTITY_FILE_SUFFIX_OUT = 40100008;
@RetLabel("未获取到身份证图片")
public static final int RET_IDENTITY_FILE_NOTEXIST = 40100009;
@RetLabel("填写信息与照片不符")
public static final int RET_IDENTITY_FILE_NOTOK = 40100010;
@RetLabel("上送人脸核身信息失败")
public static final int RET_IDENTITY_FILE_UPLOADFACEID = 40100011;
@RetLabel("身份证人像面信息读取失败")
public static final int RET_IDENTITY_FACEID_FRONT_ERROR = 40100012;
@RetLabel("身份证国徽面信息读取失败")
public static final int RET_IDENTITY_FACEID_BACK_ERROR = 40100013;
@RetLabel("未查询到实名认证申请记录")
public static final int RET_IDENTITY_FACEID_INFO_ERROR = 40100014;
@RetLabel("人脸核身识别失败")
public static final int RET_IDENTITY_FACEID_IERROR = 40100015;
@RetLabel("人脸核身返回名称或身份证号码不匹配")
public static final int RET_IDENTITY_FACEID_NOTOK = 40100016;
@RetLabel("Base64转文件失败")
public static final int RET_IDENTITY_FILE_CREATE_ERROR = 40100017;
@RetLabel("该身份证已与其他彩虹号绑定,如有疑问,请联系客服")
public static final int RET_IDENTITY_REPEAT_ERROR = 40100018;
@RetLabel("申请材料无效")
public static final int RET_IDENTITY_APPLY_FILE_ERROR = 40100019;
//----------------------------------------------消息-------------------------------------------------------------
@RetLabel("消息参数不正确")
public static final int RET_MESSAGE_ILLEGAL = 40150001;
@RetLabel("空白消息")
public static final int RET_MESSAGE_EMPTY = 40150002;
@RetLabel("消息内容超长")
public static final int RET_MESSAGE_TOO_LONG = 40150003;
@RetLabel("还不是好友关系")
public static final int RET_MESSAGE_NOT_FRIEND = 40150004;
@RetLabel("消息含有敏感词")
public static final int RET_MESSAGE_HAS_SENSITIVE_WORD = 40150005;
@RetLabel("图片信息有误")
public static final int RET_MESSAGE_PICTURE_ERROR = 40150006;
@RetLabel("用户已被禁言")
public static final int RET_MESSAGE_ACCOUNT_BANNED = 40150007;
//-----------------------------------------push-----------------------------------------------------
@RetLabel("通知未开启")
public static final int RET_PUSH_NOT_OPEN = 40160001;
//-----------------------------------------通用-----------------------------------------------------
@RetLabel("访问内容不存在")
public static final int RES_NON_EXISTENT = 40180001;
//-----------------------------------------其他临时-----------------------------------------------------
@RetLabel("内容已被锁定")
public static final int RET_ARTICLE_LOCKED = 40400001;
@RetLabel("身份证信息已存在")
public static final int RET_IDCARD_EXISTS = 40400002;
protected static final Map<String, Map<Integer, String>> rets = RetLabel.RetLoader.loadMap(RetCodes.class);
protected static final Map<Integer, String> defret = rets.get("");
public static final RetResult RET_SUCCESS = RetResult.success();
public static <T> RetResult<T> retResult(int retcode) {
if (retcode == 0) {
return RET_SUCCESS;
}
return new RetResult(retcode, retInfo(retcode));
}
public static <T> RetResult<T> retResult(String locale, int retcode) {
if (retcode == 0) {
return RET_SUCCESS;
}
return new RetResult(retcode, retInfo(locale, retcode));
}
public static <T> RetResult<T> retResult(int retcode, Object... args) {
if (retcode == 0) {
return RET_SUCCESS;
}
if (args == null || args.length < 1) {
return new RetResult(retcode, retInfo(retcode));
}
String info = MessageFormat.format(retInfo(retcode), args);
return new RetResult(retcode, info);
}
public static <T> RetResult<T> retResult(String locale, int retcode, Object... args) {
if (retcode == 0) {
return RET_SUCCESS;
}
if (args == null || args.length < 1) {
return new RetResult(retcode, retInfo(locale, retcode));
}
String info = MessageFormat.format(retInfo(locale, retcode), args);
return new RetResult(retcode, info);
}
public static <T> CompletableFuture<RetResult<T>> retResultFuture(int retcode) {
return CompletableFuture.completedFuture(retResult(retcode));
}
public static <T> CompletableFuture<RetResult<T>> retResultFuture(String locale, int retcode) {
return CompletableFuture.completedFuture(retResult(locale, retcode));
}
public static <T> CompletableFuture<RetResult<T>> retResultFuture(int retcode, Object... args) {
return CompletableFuture.completedFuture(retResult(retcode, args));
}
public static <T> CompletableFuture<RetResult<T>> retResultFuture(String locale, int retcode, Object... args) {
return CompletableFuture.completedFuture(retResult(locale, retcode, args));
}
public static RetResult set(RetResult result, int retcode, Object... args) {
if (retcode == 0) {
return result.retcode(0).retinfo("");
}
if (args == null || args.length < 1) {
return result.retcode(retcode).retinfo(retInfo(retcode));
}
String info = MessageFormat.format(retInfo(retcode), args);
return result.retcode(retcode).retinfo(info);
}
public static RetResult set(RetResult result, String locale, int retcode, Object... args) {
if (retcode == 0) {
return result.retcode(0).retinfo("");
}
if (args == null || args.length < 1) {
return result.retcode(retcode).retinfo(retInfo(locale, retcode));
}
String info = MessageFormat.format(retInfo(locale, retcode), args);
return result.retcode(retcode).retinfo(info);
}
public static String retInfo(int retcode) {
if (retcode == 0) {
return "Success";
}
return defret.getOrDefault(retcode, "Error");
}
public static String retInfo(String locale, int retcode) {
if (locale == null || locale.isEmpty()) {
return retInfo(retcode);
}
if (retcode == 0) {
return "Success";
}
String key = locale == null ? "" : locale;
Map<Integer, String> map = rets.get(key);
if (map == null) {
return "Error";
}
return map.getOrDefault(retcode, "Error");
}
public static RetResult retResult(RetResult codeRet) {
return Utils.copy(new RetResult<>(), codeRet);
}
}

View File

@ -0,0 +1,16 @@
package com.zchd.base.info;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 敏感词配置检查
*/
@Target(FIELD)
@Retention(RUNTIME)
public @interface Swear {
String name() default "";
}

View File

@ -0,0 +1,33 @@
package com.zchd.base.info;
import lombok.Getter;
import lombok.Setter;
import org.redkale.util.Comment;
import java.io.Serializable;
/**
* @author YLZ FUTURE MADE
* @date 2021/10/15 10:42
*/
@Getter
@Setter
public class SwearRpcReq implements Serializable {
@Comment("敏感词内容")
private String content;
@Comment("敏感词检测类型 MinMatchTYpe最小匹配规则敏感词库[\"中国\",\"中国人\"],语句:\"我是中国人\",匹配结果:我是[中国]人\n" +
" * MaxMatchType最大匹配规则敏感词库[\"中国\",\"中国人\"],语句:\"我是中国人\",匹配结果:我是[中国人]")
private int matchType = 1;
@Comment("替换的敏感字")
private char replaceChar = '*';
@Comment("替换的敏感词")
private String replace = "";
public static SwearRpcReq build(String content) {
SwearRpcReq bean = new SwearRpcReq();
bean.setContent(content);
return bean;
}
}

View File

@ -0,0 +1,168 @@
package com.zchd.base.info;
import com.zchd.base.BaseService;
import com.zchd.base.RetCodes;
import com.zchd.base.util.FileKit;
import com.zchd.base.util.QueueTasks;
import com.zchd.base.util.Utils;
import org.redkale.net.http.RestMapping;
import org.redkale.net.http.RestService;
import org.redkale.service.RetResult;
import org.redkale.util.AnyValue;
import javax.annotation.Resource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@RestService(name = "swearword", comment = "敏感词服务")
public class SwearWordService extends BaseService {
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
@Resource(name = "APP_HOME")
protected File APP_HOME;
@Resource(name = "property.swearbasepath")
private String swearbasepath;
private Set<String> words = new HashSet<>();
@Override
public void init(AnyValue config) {
QueueTasks.add(() -> {
reload();
zhub.subscribe("sport:swearword-reload", x -> {
reload();
});
});
}
@RestMapping(name = "reload", comment = "加载敏感词")
public RetResult reload() {
if (Utils.isEmpty(swearbasepath)) {
return RET_SUCCESS;
}
String swearpath = swearbasepath.replace("${APP_HOME}", APP_HOME.getPath());
File dir = new File(swearpath);
if (!dir.exists()) {
return null;
}
words.clear();
for (File file : dir.listFiles()) {
if (!file.isFile()) continue;
try {
FileInputStream in = new FileInputStream(file);
LineNumberReader reader = new LineNumberReader(new InputStreamReader(in, "UTF-8"));
String line;
int n = words.size();
while ((line = reader.readLine()) != null) {
words.add(Utils.unicodeToCn(line).trim());
}
words.remove("");
words.remove("null");
words.remove("9");
logger.log(Level.INFO, String.format("loaded swear file :%s, add new words: %d", file.getName(), words.size() - n));
reader.close();
} catch (FileNotFoundException | UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return RET_SUCCESS;
}
@RestMapping(name = "check", auth = false)
public RetResult check(String str) {
for (String word : words) {
if (str.contains(word)) {
RetResult<Object> result = new RetResult<>();
result.setRetcode(RetCodes.RET_SWEAR_ERROR);
result.setRetinfo(word);
FileKit.append(result.getRetinfo(), new File("/tmp/swearword.txt"));
return result;
}
}
return RET_SUCCESS;
}
/**
* 实体中属性敏感词 检查
* 调用前需配置实体属性 @Swear
*
* @param obj
* @return
*/
public RetResult _checkBean(Object obj) {
if (obj == null) {
return RET_SUCCESS;
}
Class clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Swear swear = field.getAnnotation(Swear.class);
if (swear != null) {
try {
field.setAccessible(true);
Object str = field.get(obj);
RetResult result = str instanceof String ? check((String) str) : RetResult.success();
if (!result.isSuccess()) {
String name = swear.name();
if (!Utils.isEmpty(name)) {
result.setRetinfo(name + "包含敏感词[" + result.getRetinfo() + "]");
}
return result;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return RET_SUCCESS;
}
public RetResult checkBean(Object obj) {
if (obj == null) {
return RET_SUCCESS;
}
Class clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Swear swear = field.getAnnotation(Swear.class);
if (swear != null) {
try {
field.setAccessible(true);
String str = (String) field.get(obj);
RetResult result;
do {
result = str instanceof String ? check(str) : RetResult.success();
if (!result.isSuccess()) {
FileKit.append(result.getRetinfo(), new File("/tmp/swearword.txt"));
str = str.replace(result.getRetinfo(), "*");
} else {
field.set(obj, str);
}
} while (!result.isSuccess());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return RET_SUCCESS;
}
}

View File

@ -0,0 +1,160 @@
package com.zchd.base.util;
import org.redkale.convert.json.JsonConvert;
import java.io.*;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.Files;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Arrays.asList;
/**
* Created by liangxianyou at 2018/5/31 10:23.
*/
public final class FileKit {
private FileKit() {
}
public static void strToFile(String entityBody, File file) {
strToFile(entityBody, file, true);
}
public static void strToFile(String entityBody, File file, boolean existDel) {
if (file.exists()) {
if (existDel) {
file.delete();
} else {
throw new RuntimeException(file.getPath() + "已经存在");
}
}
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
try (FileOutputStream out = new FileOutputStream(file)) {
out.write(entityBody.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static void append(String str, File file) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
try (FileOutputStream out = new FileOutputStream(file, true)) {
out.write(str.getBytes("UTF-8"));
if (!str.endsWith("\n")) {
out.write("\n".getBytes("UTF-8"));
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 拷贝文件/文件目录
*
* @param source 源文件目录
* @param target 目标目录
*/
private static void copyFiles(File source, File target) {
copyFiles(source, target, "");
}
/**
* 拷贝文件/文件目录
*
* @param source
* @param target
* @param linkPath
*/
public static void copyFiles(File source, File target, String linkPath) {
if (source.isDirectory()) {
final String _linkPath = linkPath + File.separator + source.getName();
asList(source.listFiles()).forEach(f -> {
copyFiles(f, target, _linkPath);
});
} else if (source.isFile()) {
try {
String _linkPath = "";
int index = linkPath.indexOf(File.separator, 1);
if (index > 0) {
_linkPath = linkPath.substring(index);
}
File targetFile = new File(target.toPath() + _linkPath + File.separator + source.getName());
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
Files.copy(source.toPath(), targetFile.toPath(), REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取 clazz的路径如果是jar里面的文件得到jar存放的目录lib
*
* @param clazz
* @return
*/
public static String rootPath(Class clazz) {
//return clazz.getClassLoader().getResource("").getPath();
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
try {
String filePath = URLDecoder.decode(url.getPath(), "utf-8");
if (filePath.endsWith(".jar")) {
return filePath.substring(0, filePath.lastIndexOf("/") + 1);
}
return filePath;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
public static String rootPath() {
return rootPath(FileKit.class);
}
/**
* 读取流内的所有内容
*
* @param inputStream
* @return
* @throws IOException
*/
public static String readAll(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuffer buf = new StringBuffer();
String str;
try {
while ((str = reader.readLine()) != null) {
buf.append(str + "\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return buf.toString();
}
public static <T> T readAs(File file, Type typeToken) throws IOException {
try (
FileInputStream inputStream = new FileInputStream(file)
) {
return JsonConvert.root().convertFrom(typeToken, inputStream);
}
}
}

View File

@ -0,0 +1,325 @@
package com.zchd.base.util;
import org.redkale.convert.json.JsonConvert;
import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Created by liangxianyou@eversec.cn at 2018/3/12 14:17.
*/
public class Kv<K, V> extends LinkedHashMap<K, V> {
public static Kv of() {
return new Kv();
}
public static Kv of(Object k, Object v) {
return new Kv().set(k, v);
}
public static Kv filter(Map<String, String> map, String... fields) {
Kv kv = Kv.of();
if (fields == null || fields.length == 0 || map == null) {
return kv;
}
for (String field : fields) {
if (field.contains("->")) {
String[] arr = field.split("->");
kv.put(arr[1], map.get(arr[0]));
continue;
}
kv.put(field, map.get(field));
}
return kv;
}
public Kv<K, V> set(K k, V v) {
put(k, v);
return this;
}
public Kv<K, V> putAll(Kv<K, V> kv) {
kv.forEach((k, v) -> put(k, v));
return this;
}
// 将obj 属性映射到Kv
public static Kv toKv(Object m, String... fields) {
Kv kv = Kv.of();
if (m == null) {
return kv;
}
Stream.of(fields).forEach(field -> {
String filedT = field;
String filedS = field;
try {
if (field.contains("=")) {
String[] arr = field.split("=");
filedT = arr[0];
filedS = arr[1];
}
Method method = m.getClass().getMethod("get" + Utils.toUpperCaseFirst(filedS));
if (method != null) {
kv.set(filedT, method.invoke(m));
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
new IllegalArgumentException(String.format("Kv.toKv获取 获取参数[]失败", field), e);
}
});
return kv;
}
public static <T> List<Kv> toKv(Collection<T> datas, String... fields) {
return datas.stream().map(x -> toKv(x, fields)).collect(Collectors.toList());
}
public static Kv toKv(Object m) {
return toKv(m, Kv.of(), m.getClass());
}
private static Kv toKv(Object m, Kv kv, Class clazz) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.getName().startsWith("get") || method.getParameterCount() > 0 || "getClass".equals(method.getName()))
continue;
String k = Utils.toLowerCaseFirst(method.getName().replaceFirst("get", ""));
if (!kv.containsKey(k) || Utils.isEmpty(kv.get(k))) {
try {
kv.set(k, method.invoke(m));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
for (Field field : clazz.getDeclaredFields()) {
if (field.getAnnotation(Id.class) != null) {
try {
field.setAccessible(true);
kv.set("_id", field.get(m));
break;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
Class superclass = clazz.getSuperclass();
if (superclass != null) {
kv = toKv(m, kv, superclass);
}
return kv;
}
public <T> T toBean(Class<T> type) {
return toBean(this, type);
}
// 首字母大写
private static Function<String, String> upFirst = (s) -> {
return s.substring(0, 1).toUpperCase() + s.substring(1);
};
private static Predicate<Class> isNumber = (t) -> {
return t == Integer.class || t == int.class
|| t == Long.class || t == long.class
|| t == Float.class || t == float.class
|| t == Double.class || t == double.class
|| t == Short.class || t == short.class
|| t == Byte.class || t == byte.class
;
};
public static <T> T toAs(Object v, Class<T> clazz) {
if (v == null) {
return null;
} else if (v.getClass() == clazz) {
return (T) v;
} else if (clazz == String.class) {
return (T) String.valueOf(v);
}
Object v1 = v;
try {
if (v.getClass() == Long.class) {//多种数值类型的处理: Long => x
switch (clazz.getSimpleName()) {
case "int", "Integer" -> v1 = (int) (long) v;
case "short", "Short" -> v1 = (short) (long) v;
case "float", "Float" -> v1 = (float) (long) v;
case "byte", "Byte" -> v1 = (byte) (long) v;
}
} else if (v.getClass() == Double.class) {
if (isNumber.test(clazz)) {
switch (clazz.getSimpleName()) {
case "long", "Long" -> v1 = (long) (double) v;
case "int", "Integer" -> v1 = (int) (double) v;
case "short", "Short" -> v1 = (short) (double) v;
case "float", "Float" -> v1 = (float) (double) v;
case "byte", "Byte" -> v1 = (byte) (double) v;
}
} else if (clazz == String.class) {
v1 = String.valueOf(v);
}
} else if (v.getClass() == String.class) {
switch (clazz.getSimpleName()) {
case "Date" -> v1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse((String) v);
case "short", "Short" -> v1 = (short) Double.parseDouble((String) v);
case "float", "Float" -> v1 = (float) Double.parseDouble((String) v);
case "int", "Integer" -> v1 = (int) Double.parseDouble((String) v);
case "long", "Long" -> v1 = (long) Double.parseDouble((String) v);
case "double", "Double" -> v1 = Double.parseDouble((String) v);
case "byte", "Byte" -> v1 = Byte.parseByte((String) v);
}
} else if (v.getClass() == Integer.class) {
switch (clazz.getSimpleName()) {
case "long", "Long" -> v1 = (long) (int) v;
case "short", "Short" -> v1 = (short) (int) v;
case "float", "Float" -> v1 = (float) (int) v;
case "byte", "Byte" -> v1 = (byte) (int) v;
}
} else if (v.getClass() == Float.class) {
switch (clazz.getSimpleName()) {
case "long", "Long" -> v1 = (long) (float) v;
case "int", "Integer" -> v1 = (int) (float) v;
case "short", "Short" -> v1 = (short) (float) v;
case "byte", "Byte" -> v1 = (byte) (float) v;
}
} else {
v1 = v;
}
} catch (ParseException e) {
e.printStackTrace();
}
return (T) v1;
}
public static <T> T toBean(Map map, Class<T> clazz) {
//按照方法名 + 类型寻找
//按照方法名 寻找
//+
Object obj = null;
try {
obj = clazz.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
new IllegalArgumentException("创建对象实列失败", e); // 检查clazz是否有无参构造
}
for (String k : (Set<String>) map.keySet()) {
Object v = map.get(k);
if (v == null) continue;
//寻找method
try {
String methodName = "set" + upFirst.apply(k);
Class tClazz = null;
Method method = null;
try {
method = clazz.getMethod(methodName, tClazz = v.getClass());
} catch (NoSuchMethodException e) {
//e.printStackTrace();
}
if (method == null) {
for (Method _method : clazz.getMethods()) {
if (methodName.equals(_method.getName()) && _method.getParameterCount() == 1) {
method = _method;
tClazz = _method.getParameterTypes()[0];
}
}
}
if (method == null) {
for (Method _method : clazz.getMethods()) {
if (methodName.equalsIgnoreCase(_method.getName()) && _method.getParameterCount() == 1) {
method = _method;
tClazz = _method.getParameterTypes()[0];
}
}
}
if (method != null) {
method.invoke(obj, toAs(v, tClazz));
}
//没有方法找属性注解
/*if (method == null) {
Field field = null;
Field[] fields = clazz.getDeclaredFields();
for (Field _field : fields) {
To to = _field.getAnnotation(To.class);
if (to != null && k.equals(to.value())) {
field = _field;
tClazz = _field.getType();
break;
}
}
if (field != null) {
field.setAccessible(true);
field.set(obj, toAs(v, tClazz));
}
}*/
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
return (T) obj;
}
public String toString() {
return JsonConvert.root().convertTo(this);
}
public int getInt(String key) {
return getInt(key, 0);
}
public int getInt(String key, int defaultValue) {
V v = get(key);
if (v == null) {
return defaultValue;
}
return toAs(v, int.class);
}
public long getLong(String key) {
return getLong(key, 0);
}
public long getLong(String key, long defaultValue) {
V v = get(key);
if (v == null) {
return defaultValue;
}
return toAs(v, long.class);
}
public String getStr(String key) {
return toAs(get(key), String.class);
}
public String getStr(String key, String defaultValue) {
V v = get(key);
if (v == null) {
return defaultValue;
}
return toAs(v, String.class);
}
}

View File

@ -0,0 +1,146 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.zchd.base.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @param <T> 泛型
* @author zhangjx
*/
public class QueueTask<T> {
private static List<QueueTask> queueTasks = new ArrayList<>(); // queueTask 实例引用
private static final AtomicInteger counter = new AtomicInteger();
protected final BlockingQueue<T> queue;
protected final int threads;
protected Consumer<T> consumer;
protected Logger logger;
public QueueTask(int threads) {
this.threads = threads;
this.queue = new LinkedBlockingQueue<>();
}
public QueueTask(int threads, int queueSize) {
this.threads = threads;
this.queue = new LinkedBlockingQueue<>(queueSize);
}
public QueueTask(int threads, Logger logger, Consumer<T> consumer) {
this.threads = threads;
this.queue = new LinkedBlockingQueue<>();
this.init(logger, consumer);
}
public T poll() {
return this.queue.poll();
}
public T task() throws InterruptedException {
return this.queue.take();
}
public int size() {
return this.queue.size();
}
public boolean add(T data) {
return this.queue.add(data);
}
public boolean remove(T data) {
return this.queue.remove(data);
}
public void put(T data) throws InterruptedException {
this.queue.put(data);
}
public void init(Logger logger, Consumer<T> consumer) {
this.logger = logger;
this.consumer = consumer;
Runnable task = () -> {
T data;
try {
while ((data = queue.take()) != null) {
try {
consumer.accept(data);
} catch (Throwable e) {
if (logger != null) logger.log(Level.SEVERE, "QueueTask Data["
+ (data == null ? null : data.getClass().getSimpleName()) + "](" + data + ") consume error", e);
}
}
} catch (InterruptedException ex) {
}
};
for (int i = 0; i < threads; i++) {
Thread thread = new Thread(task);
thread.setName("QueueTask-" + i + "-Thread");
thread.setDaemon(true);
thread.start();
}
counter.addAndGet(threads);
queueTasks.add(this);
}
public void destroy() {
int count = 0;
while (count < 50) {
if (queue.size() > 0) {
try {
Thread.sleep(200);
} catch (Exception e) {
break;
}
count++;
} else {
count = Integer.MAX_VALUE;
}
}
counter.addAndGet(-threads);
}
public static int runningThreads() {
return counter.get();
}
// 队列堆叠信息获取
public static List<Kv> pileup() {
Map map = new HashMap();
for (QueueTask queueTask : queueTasks) {
map.put(queueTask.logger.getName(), queueTask.queue.size());
}
List<Kv> kvs = Utils.toList(queueTasks, x -> {
Kv kv = Kv.of();
kv.set("name", x.logger.getName().replace("_DynLocal", ""));
kv.set("threadcount", x.threads);
kv.set("stack", x.queue.size());
return kv;
});
return kvs;
}
public static void destroys() {
for (QueueTask queueTask : queueTasks) {
queueTask.destroy();
}
}
}

View File

@ -0,0 +1,38 @@
package com.zchd.base.util;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
/**
* 公共异步队列
*
* @author: liangxy.
*/
public class QueueTasks {
private static final QueueTask<Runnable> queueTask = new QueueTask<>(1);
static {
queueTask.init(Logger.getLogger(QueueTasks.class.getSimpleName()), Runnable::run);
}
public static void add(Runnable runnable) {
queueTask.queue.add(runnable);
}
// -------------------------- 支持返回结果的任务队列 -----------------------------
private static ExecutorService executor = Executors.newFixedThreadPool(1);
public static CompletableFuture submit(Runnable task) {
return CompletableFuture.runAsync(() -> {
try {
executor.submit(task).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,269 @@
package com.zchd.zim;
import com.zchd.base.BaseService;
import com.zchd.base.util.Kv;
import com.zchd.zim.bean.ImBean;
import com.zchd.zim.bean.SubscribeBean;
import com.zchd.zim.entity.AppInfo;
import com.zchd.zim.entity.ChannelUser;
import com.zchd.zim.entity.ImUser;
import com.zdemo.cachex.MyRedisCacheSource;
import com.zdemo.zhub.RpcResult;
import org.redkale.convert.json.JsonConvert;
import org.redkale.net.http.RestMapping;
import org.redkale.net.http.RestService;
import org.redkale.service.RetResult;
import org.redkale.source.*;
import org.redkale.util.AnyValue;
import org.redkale.util.Comment;
import org.redkale.util.TypeToken;
import org.redkale.util.Utility;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestService(name = "im_account", comment = "账号服务")
public class ImAccountService extends BaseService {
@Resource(name = "str_cache")
protected MyRedisCacheSource<String> strCache;
@Resource
protected ImMessageMonitor messageMonitor;
@Resource
protected ImAccountService accountService;
@Override
public void init(AnyValue config) {
CompletableFuture.runAsync(() -> {
List<Serializable> list = zimSource.queryColumnList("userid", ImUser.class, new Flipper(1, "userid DESC"), (FilterBean) null);
int userid = 10000;
if (!list.isEmpty()) {
userid = Kv.toAs(list.get(0), int.class);
}
intCache.set("im:max-userid", userid);
});
// 游戏用户注册
zhub.rpcSubscribe("im:account-register", new TypeToken<ImBean>() {
}, r -> {
ImBean bean = r.getValue();
RetResult<Kv> result = register(bean);
if (!result.isSuccess()) {
return r.buildError(result.getRetinfo());
}
return r.buildResp(result.getResult());
});
// 获取游戏用户 IM-TOKEN
zhub.rpcSubscribe("im:account-token", new TypeToken<ImBean>() {
}, r -> {
ImBean bean = r.getValue();
RetResult<Kv> result = getToken(bean);
if (!result.isSuccess()) {
return r.buildError(result.getRetinfo());
}
return r.buildResp(result.getResult());
});
// 为用户新增订阅频道
zhub.rpcSubscribe("im:subscribe", new TypeToken<SubscribeBean>() {
}, r -> {
SubscribeBean bean = r.getValue();
RetResult result = subscribe(bean);
return r.buildResp(result.getResult());
});
// 为用户取消订阅频道
zhub.rpcSubscribe("im:unsubscribe", new TypeToken<SubscribeBean>() {
}, r -> {
SubscribeBean bean = r.getValue();
RetResult result = unsubscribe(bean);
return r.buildResp(result.getResult());
});
// 禁言 todo 禁言时长 禁言指定频道
zhub.rpcSubscribe("im:banned-talk", new TypeToken<SubscribeBean>() {
}, r -> {
SubscribeBean bean = r.getValue();
String imtoken = bean.getImtoken();
ImUser user = accountService.currentImUser(imtoken);
int userid = user.getUserid();
zimSource.updateColumn(ImUser.class, userid, ColumnValue.create("status", ImUser.STATUS_FREEZE_ACTIVE));
intCache.setBit("im:banned-talk", userid, true);
return r.buildResp();
});
// 取消禁言
zhub.rpcSubscribe("im:unbanned-talk", new TypeToken<SubscribeBean>() {
}, r -> {
SubscribeBean bean = r.getValue();
String imtoken = bean.getImtoken();
ImUser user = accountService.currentImUser(imtoken);
int userid = user.getUserid();
zimSource.updateColumn(ImUser.class, userid, ColumnValue.create("status", 10));
intCache.setBit("im:banned-talk", userid, false);
return r.buildResp();
});
}
@Comment("获取当前用户ID")
public ImUser currentImUser(String token) {
ImUser user = strCache.hget("im:user-token", token, new TypeToken<ImUser>() {
}.getType());
if (user == null) {
user = zimSource.find(ImUser.class, FilterNode.create("imtoken", token));
strCache.hset("im:user-token", token, JsonConvert.root(), user);
}
return user;
}
/*@Comment("获取当前用户ID")
public int currentImUserId(String token) {
return currentImUser(token).getUserid();
}*/
/*
Url: http://127.0.0.1:8021/im_account/register
Body: {"appid":"krlq07sx","appsecret":"79eb45ebdabc4d90bfb69949b8fd767e","guserid":"S_C_01_R0001","gender":10,"nickname":"M","face":"http://xxxxxx/xxx"}
Body: {"appid":"krlq07sx","appsecret":"79eb45ebdabc4d90bfb69949b8fd767e","guserid":"S_C_01_R0002","gender":20,"nickname":"V","face":"http://xxxxxx/xxx"}
*/
@RestMapping(name = "register", auth = false, comment = "获取当前登录用户ID")
public RetResult<Kv> register(ImBean bean) {
RetResult<Kv> result = check(bean);
if (!result.isSuccess()) {
return result;
}
// 检查有无当前用户
FilterNode node = FilterNode.create("guserid", bean.getGuserid()).and("appid", bean.getAppid());
ImUser user = zimSource.find(ImUser.class, node);
if (user == null) {
user = ImUser.buildImUser(bean.getGuserid(), bean.getAppid());
int userid = (int) intCache.incr("im:max-userid");
user.setUserid(userid);
user.setImtoken(Utility.uuid()); // 生成IM-TOKEN
zimSource.insert(user);
// intCache.set("im:account-user-token:" + user.getImtoken(), userid);
strCache.hset("im:user-token", user.getImtoken(), JsonConvert.root(), user);
}
return RetResult.success(Kv.of("token", user.getImtoken()));
}
/*
Url: http://127.0.0.1:8021/im_account/register
Body: {"appid":"krlq07sx","appsecret":"79eb45ebdabc4d90bfb69949b8fd767e","guserid":"S_C_01_R0001"}
Body: {"appid":"krlq07sx","appsecret":"79eb45ebdabc4d90bfb69949b8fd767e","guserid":"S_C_01_R0002"}
*/
@RestMapping(name = "get_token", auth = false, comment = "获取平台识别Token")
public RetResult<Kv> getToken(ImBean bean) {
RetResult<Kv> result = check(bean);
if (!result.isSuccess()) {
return result;
}
FilterNode node = FilterNode.create("appid", bean.getAppid()).and("guserid", bean.getGuserid());
Serializable imtoken = zimSource.findColumn(ImUser.class, "imtoken", node);
return RetResult.success(Kv.of("token", imtoken));
}
/*
Url: http://127.0.0.1:8021/im_app/subscribe
Body: {"imtoken":"krlq07sxS_C_01_R00011627473704040","channeltype":"Word","channelvalue":"W_C_01_001"}
Body: {"imtoken":"krlq07sxS_C_01_R00021627474715105","channeltype":"Word","channelvalue":"W_C_01_001"}
*/
@RestMapping(name = "subscribe", auth = false, comment = "给用户订阅频道信息")
public RetResult subscribe(SubscribeBean bean) {
ImUser user = accountService.currentImUser(bean.getImtoken());
int userid = user.getUserid();
try {
// 添加数据库订阅关系
intCache.tryLock("im:channel-create:" + userid, 500);
FilterNode node1 = FilterNode.create("appid", user.getAppid())
.and("channeltype", bean.getChanneltype())
.and("channelvalue", bean.getChannelvalue())
.and("userid", userid);
if (!zimSource.exists(ChannelUser.class, node1)) {
ChannelUser record = ChannelUser.buildChannelUser(userid, user.getAppid(), bean);
zimSource.insert(record);
// 确保写入库后再发送订阅事件
messageMonitor.subscribeChannel(userid, record.buildChannelid(), true);
}
} finally {
intCache.unlock("im:channel-create:" + userid);
}
return RET_SUCCESS;
}
/*
Url: http://127.0.0.1:8021/im_app/unsubscribe
Body: {"imtoken":"krlq07sxS_C_01_R00011627473704040","channeltype":"Word","channelvalue":"W_C_01_001"}
Body: {"imtoken":"krlq07sxS_C_01_R00021627474715105","channeltype":"Word","channelvalue":"W_C_01_001"}
*/
@RestMapping(name = "unsubscribe", auth = false, comment = "取消频道订阅")
public RetResult unsubscribe(SubscribeBean bean) {
ImUser user = accountService.currentImUser(bean.getImtoken());
int userid = user.getUserid();
String channelid = user.getAppid() + "-" + bean.getChanneltype() + "-" + bean.getChannelvalue();
messageMonitor.subscribeChannel(userid, channelid, false);
// 删除数据库订阅关系
FilterNode node1 = FilterNode.create("appid", user.getAppid())
.and("channeltype", bean.getChanneltype())
.and("channelvalue", bean.getChannelvalue())
.and("userid", userid);
zimSource.delete(ChannelUser.class, node1);
return RET_SUCCESS;
}
@Comment("平台安全校验")
public RetResult check(ImBean bean) {
FilterNode node = FilterNode.create("appid", bean.getAppid()).and("appsecret", bean.getAppsecret()).and("status", 10);
if (!zimSource.exists(AppInfo.class, node)) {
return retError("平台信息校验失败");
}
return RET_SUCCESS;
}
// ---------------------------------- 测试 ----------------------------------
@RestMapping(name = "test_account_register", auth = false, comment = "rpc测试")
public RpcResult testAccountRegister(ImBean bean) {
return zhub.rpc("im:account-register", bean);
}
@RestMapping(name = "test_account_token", auth = false, comment = "rpc测试")
public RpcResult testAccountToken(ImBean bean) {
return zhub.rpc("im:account-token", bean);
}
@RestMapping(name = "test_subscribe", auth = false, comment = "rpc测试")
public RpcResult testSubscribe(SubscribeBean bean) {
return zhub.rpc("im:subscribe", bean);
}
@RestMapping(name = "test_unsubscribe", auth = false, comment = "rpc测试")
public RpcResult testUnsubscribe(SubscribeBean bean) {
return zhub.rpc("im:unsubscribe", bean);
}
}

View File

@ -0,0 +1,193 @@
package com.zchd.zim;
import com.zchd.base.BaseService;
import com.zchd.base.util.Kv;
import com.zchd.base.util.QueueTask;
import com.zchd.zim.entity.ChannelMessage;
import com.zchd.zim.entity.ChannelUser;
import com.zchd.zim.entity.FriendMessage;
import com.zchd.zim.entity.ImUser;
import com.zdemo.zhub.ZHubClient;
import net.tccn.timer.Timers;
import org.redkale.net.http.RestService;
import org.redkale.net.http.WebSocketNode;
import org.redkale.source.ColumnValue;
import org.redkale.source.DataSource;
import org.redkale.source.FilterNode;
import org.redkale.source.Flipper;
import org.redkale.util.AnyValue;
import org.redkale.util.Comment;
import javax.annotation.Resource;
import javax.persistence.Transient;
import java.util.List;
import static org.redkale.source.FilterExpress.*;
@RestService(name = "im_chat_x", comment = "IM消息总线")
public class ImChatService extends BaseService {
@Resource(name = "im_chat")
protected WebSocketNode wsnode;
@Resource(name = "z_im")
protected DataSource zimSource;
@Resource(name = "zhub")
protected ZHubClient zhub;
@Transient //消息发送
protected QueueTask<Runnable> msgQueue = new QueueTask<>(20);
@Transient //消息存储
protected QueueTask<Runnable> dbQueue = new QueueTask<>(1);
@Override
public void init(AnyValue config) {
msgQueue.init(logger, Runnable::run);
dbQueue.init(logger, Runnable::run);
}
@Comment("发送私聊消息")
public void sendMsg(FriendMessage bean, int uid) {
wsnode.sendMessage(buildFriendMessageDeail(bean), uid).thenAccept(x -> {
if (x != 0) {
return;
}
// intCache.incrHm("im:heartbeat:" + uid, "messagecount", -1);
// 更新状态失败 200ms 后重试当数据未写入的时候更新失败
Timers.tryDelay(() -> {
FilterNode node = FilterNode.create("senduserid", bean.getSenduserid()).and("receiveuserid", uid);
int updateColumn = zimSource.updateColumn(FriendMessage.class, node, ColumnValue.create("status", 10));
return updateColumn != 0;
}, 200, 3);
});
}
@Comment("拉取离线消息")
public void pullOfflineMsg(int userid) {
msgQueue.add(() -> {
// 拉取私聊离线消息
List<FriendMessage> list = zimSource.queryList(FriendMessage.class, new Flipper(0, "createtime"),
FilterNode.create("receiveuserid", userid).and("status", 20));
list.forEach(x -> {
Integer join = wsnode.sendMessage(buildFriendMessageDeail(x), userid).join();
if (join == 0) {
zimSource.updateColumn(FriendMessage.class, x.getMessageid(), ColumnValue.create("status", 10));
}
});
// 拉取频道离线消息
List<ChannelUser> userGroups = userChannels(userid);
for (ChannelUser userGroup : userGroups) {
Long lastAcceptTime = longCache.getHm("im:channel:" + userGroup.buildChannelid(), userGroup.getUserid());
if (lastAcceptTime == null || lastAcceptTime == 0) {
lastAcceptTime = userGroup.getCreatetime();
}
// 获取当前频道的数据列表
FilterNode node1 = FilterNode.create("appid", userGroup.getAppid())
.and("channeltype", userGroup.getChanneltype())
.and("channelvalue", userGroup.getChannelvalue())
.and("senduserid", NOTEQUAL, userid)
.and("createtime", GREATERTHAN, lastAcceptTime)
.and("createtime", LESSTHAN, System.currentTimeMillis())
.and("status", NOTEQUAL, 80);
try {
List<ChannelMessage> messages = zimSource.queryList(ChannelMessage.class, new Flipper(50, "createtime desc"), node1);
// 获取最新的消息集合后从时间最早的开始推送
for (int i = messages.size() - 1; i > -1; i--) {
ChannelMessage message = messages.get(i);
int status = wsnode.sendMessage(buildMessageDetail(message), userid).join();
if (status != 0) {
return;
}
// 每次推完一个消息后将拉取时间修改为当前消息的发送时间
lastAcceptTime = message.getCreatetime();
}
} finally {
longCache.setHm("im:channel:" + userGroup.buildChannelid(), userGroup.getUserid(), lastAcceptTime);
// 变更最后拉取时间
/*FilterNode node = FilterNode.create("appid", userGroup.getAppid())
.and("channeltype", userGroup.getChanneltype())
.and("channelvalue", userGroup.getChannelvalue()).and("userid", userid);
zimSource.updateColumn(ChannelUser.class, node, ColumnValue.create("lastaccepttime", lastAcceptTime)); // todo 中途失败时间有问题*/
}
}
});
}
public List<ChannelUser> userChannels(int userid) {
FilterNode node = FilterNode.create("userid", userid)
.and(FilterNode.create("status", 10).or("status", 40));
return zimSource.queryList(ChannelUser.class, node);
}
@Comment("用户IM是否在线")
public boolean userOnline(int userid) {
return intCache.getBit("im:user", userid);
}
@Comment("用户在线直接发送, 如果不在线先记录到数据库")
public void sendFriendMessage(FriendMessage message) {
dbQueue.add(() -> {
zimSource.insert(message);
});
int receiveuserid = message.getReceiveuserid();
if (userOnline(receiveuserid)) {
zhub.publish("im:friend:" + receiveuserid, message);
}
}
public void sendChannelMessage(ChannelMessage message) {
// 记录消息发送群消息
dbQueue.add(() -> {
zimSource.insert(message);
});
zhub.broadcast("im:channel:" + message.buildChannelid(), message);
}
private String getGuserid(int userid) {
ImUser user = zimSource.find(ImUser.class, userid);
return user.getGuserid();
}
public int getUserid(String appid, String guserid) {
ImUser user = zimSource.find(ImUser.class, FilterNode.create("appid", appid).and("guserid", guserid));
return user.getUserid();
}
public Kv buildFriendMessageDeail(FriendMessage message) {
Kv detail = Kv.toKv(message, "content", "messageid", "sendtime");
detail.set("sendguserid", getGuserid(message.getSenduserid()));
Kv data = Kv.of("detail", detail).set("type", "friend-text");
return data;
}
public Kv buildBackMessage(FriendMessage message, String mck) {
Kv detail = Kv.toKv(message, "content", "messageid", "sendtime");
detail.set("sendguserid", getGuserid(message.getSenduserid()));
Kv data = Kv.of("detail", detail).set("type", "friend-text").set("mck", mck);
return data;
}
public Kv buildMessageDetail(ChannelMessage message) {
Kv detail = Kv.toKv(message, "content", "messageid", "sendtime", "channeltype", "channelvalue");
detail.set("sendguserid", getGuserid(message.getSenduserid()));
Kv data = Kv.of("detail", detail).set("type", "channel-text");
return data;
}
public Kv buildBackMessage(ChannelMessage message, String mck) {
Kv detail = Kv.toKv(message, "content", "messageid", "sendtime", "channeltype", "channelvalue");
detail.set("sendguserid", getGuserid(message.getSenduserid()));
Kv data = Kv.of("detail", detail).set("type", "channel-text").set("mck", mck);
return data;
}
}

View File

@ -0,0 +1,182 @@
package com.zchd.zim;
import com.zchd.base.info.SwearWordService;
import com.zchd.base.util.Kv;
import com.zchd.base.util.Utils;
import com.zchd.zim.entity.ChannelMessage;
import com.zchd.zim.entity.FriendMessage;
import com.zchd.zim.entity.ImUser;
import com.zdemo.cachex.MyRedisCacheSource;
import org.redkale.net.http.HttpRequest;
import org.redkale.net.http.RestOnMessage;
import org.redkale.net.http.RestWebSocket;
import org.redkale.net.http.WebSocket;
import org.redkale.service.RetResult;
import org.redkale.source.DataSource;
import org.redkale.util.Comment;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/*
* ws://127.0.0.1:8091/im_chat?imtoken=xx
* */
@RestWebSocket(name = "im_chat", comment = "IM即使通讯", anyuser = true)
public class ImChatWebSocket extends WebSocket {
@Resource(name = "z_im")
protected DataSource zimSource;
@Resource(name = "int_cache")
protected MyRedisCacheSource<Integer> intCache;
@Resource
protected ImAccountService accountService;
@Resource
protected ImChatService chatService;
@Resource
protected ImMessageMonitor messageMonitor;
@Resource
protected SwearWordService swearWordService;
@Override
protected CompletableFuture<String> onOpen(HttpRequest request) {
return CompletableFuture.supplyAsync(() -> {
String token = request.getHeader("imtoken", request.getParameter("imtoken"));
return token;
});
}
@Override
protected CompletableFuture createUserid() {
return CompletableFuture.supplyAsync(() -> {
ImUser user = accountService.currentImUser(getSessionid());
int userid = user.getUserid();
if (userid > 0) {
WebSocket socket = findLocalWebSocket(userid);
if (socket != null) {
forceCloseWebSocket(userid);
}
setAttribute("appid", user.getAppid());
setAttribute("guserid", user.getGuserid());
}
return userid;
});
}
@Override
public CompletableFuture onConnected() {
return CompletableFuture.runAsync(() -> {
final int userid = (int) getUserid();
getLogger().info("im:state-change:" + userid + "---ws connected---");
intCache.setBit("im:user", userid, true);
// 上线开启订阅
messageMonitor.online(userid);
// 拉取离线消息
chatService.pullOfflineMsg(userid);
});
}
/*
{friend:{bean:{"type": "friend-text","content": "发给V的私聊"},extmap:{"mck":"11001and1234567860","guserid": "S_C_01_R0002"}}}
{friend:{bean:{"type": "friend-text","content": "发给M的私聊"},extmap:{"mck":"11001and1234567861","guserid": "S_C_01_R0001"}}}
*/
@RestOnMessage(name = "friend", comment = "私聊消息")
public void friend(@Comment("content、type") Map<String, String> bean,
@Comment("mck、guserid") Map<String, String> extmap) {
int userid = (int) getUserid();
String guserid = extmap.get("guserid");
String appid = (String) getAttribute("appid");
String mck = extmap.get("mck");
int targetguserid = chatService.getUserid(appid, guserid);
FriendMessage message = FriendMessage.buildFriendMessage(bean.get("content"), userid, targetguserid);
// 禁言检查
if (intCache.getBit("im:banned-talk", userid)) {
sendTip("发送失败,你已被禁言", mck, 3002);
return;
}
// 检查敏感词 若加入消息撤回可使用延时检查后置审核检查到违禁词 撤回消息
RetResult check = swearWordService.check(message.getContent());
if (!check.isSuccess()) {
sendTip("发送消息含有敏感词", mck, 3001);
return;
}
// 发送消息
chatService.sendFriendMessage(message);
// 回执
Kv kv = chatService.buildBackMessage(message, mck);
send(kv);
}
/*
{channel:{bean:{"type": "channel-test","content": "asfsafasfsaf"},extmap:{"mck":"11001and1234567860","channeltype": "world","channelvalue": "2"}}}
{channel:{bean:{"type": "channel-test","content": "这是世界频道消息V"},extmap:{"mck":"11001and1234567860","channeltype": "Word","channelvalue": "W_C_01_001"}}}
*/
@RestOnMessage(name = "channel", comment = "频道消息")
public void channel(@Comment("content、type") Map<String, String> bean,
@Comment("mck、channeltype、channelvalue") Map<String, String> extmap) {
int userid = (int) getUserid();
String appid = (String) getAttribute("appid");
String channeltype = extmap.get("channeltype");
String channelvalue = extmap.get("channelvalue");
String mck = extmap.get("mck");
// 禁言检查
if (intCache.getBit("im:banned-talk", userid)) {
sendTip("发送失败,你已被禁言", mck, 3002);
return;
}
// 检查敏感词 若加入消息撤回可使用延时检查后置审核检查到违禁词 撤回消息
RetResult check = swearWordService.check(bean.get("content"));
if (!check.isSuccess()) {
sendTip("发送消息含有敏感词", mck, 3001);
return;
}
ChannelMessage message = new ChannelMessage();
message.setContent(bean.get("content"));
message.setAppid(appid);
message.setSenduserid(userid);
message.setChanneltype(channeltype);
message.setChannelvalue(channelvalue);
message.setCreatetime(System.currentTimeMillis());
message.setStatus((short) 20);
message.setMessageid(Utils.fmt36(message.getSenduserid()) + "-" + Utils.fmt36(message.getCreatetime()));
// 发送
chatService.sendChannelMessage(message);
// 回执
Kv kv = chatService.buildBackMessage(message, mck);
send(kv);
}
@Override
public CompletableFuture onClose(int code, String reason) {
final int userid = (int) getUserid();
getLogger().info("im:state-change:" + userid + "---close---" + code + "---" + reason);
intCache.setBit("im:user:", userid, false);
// 取消用户订阅
messageMonitor.offline(userid);
return null;
}
@Comment("发送提示信息")
public void sendTip(String tipinfo, String mck, int code) {
Kv tip = Kv.of("type", "tip")
.set("mck", mck).set("code", code)
.set("info", tipinfo);
send(tip);
}
}

View File

@ -0,0 +1,166 @@
package com.zchd.zim;
import com.zchd.base.BaseService;
import com.zchd.base.util.Kv;
import com.zchd.base.util.QueueTask;
import com.zchd.base.util.Utils;
import com.zchd.zim.entity.ChannelMessage;
import com.zchd.zim.entity.ChannelUser;
import com.zchd.zim.entity.FriendMessage;
import com.zdemo.cachex.MyRedisCacheSource;
import org.redkale.net.http.RestService;
import org.redkale.net.http.WebSocketNode;
import org.redkale.util.AnyValue;
import org.redkale.util.Comment;
import org.redkale.util.TypeToken;
import org.redkale.util.Utility;
import javax.annotation.Resource;
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
@RestService(name = "im_message_monitor", comment = "总线消息订阅管理")
public class ImMessageMonitor extends BaseService {
@Resource
protected ImChatService chatService;
@Resource(name = "im_chat")
protected WebSocketNode wsnode;
@Resource(name = "int_cache")
protected MyRedisCacheSource<Integer> intCache;
@Resource(name = "long_cache")
protected MyRedisCacheSource<Long> longCache;
protected BitSet bitSet = new BitSet(); // IM用户连接当前实例记录
protected ConcurrentHashMap<String, Set<Integer>> channelSubscribe = new ConcurrentHashMap<>();
protected final QueueTask<Runnable> messageQueue = new QueueTask<>(1);
@Override
public void init(AnyValue config) {
messageQueue.init(Logger.getLogger(this.getClass().getSimpleName()), Runnable::run);
// 订阅用户动态广播消息 用户未连接当前IM 服务实例不做通道订阅
zhub.subscribe("im:channel", new TypeToken<Kv<String, String>>() {
}, para -> {
int userid = para.getInt("userid");
if (!bitSet.get(userid)) {
return;
}
String channelid = para.get("channelid");
String type = para.get("type");
switch (type) {
case "subscribe" -> subscribeChannel(userid, channelid);
case "unsubscribe" -> unsubscribeChannel(userid, channelid);
}
});
}
@Comment("用户上线")
public void online(int userid) {
// 设置用户在当前实例标记
bitSet.set(userid);
// 开启频道订阅
List<ChannelUser> list = chatService.userChannels(userid);
list.forEach(x -> {
subscribeChannel(userid, x.buildChannelid());
});
// 开启对点订阅
zhub.subscribe("im:friend:" + userid, new TypeToken<FriendMessage>() {
}, x -> {
messageQueue.add(() -> {
chatService.sendMsg(x, userid);
});
});
}
@Comment("用户下线")
public void offline(int userid) {
// 设置用户在当前实例标记
bitSet.clear(userid);
// 取消点对点订阅
zhub.unsubscribe("im:friend:" + userid);
// 取消频道订阅
List<ChannelUser> list = chatService.userChannels(userid);
list.forEach(x -> {
unsubscribeChannel(userid, x.buildChannelid());
});
}
@Comment("订阅频道")
private void subscribeChannel(int userid, String channelid) {
Set<Integer> uids = channelSubscribe.get(channelid);
if (uids == null) {
uids = new HashSet<>();
zhub.subscribe("im:channel:" + channelid, new TypeToken<ChannelMessage>() {
}, x -> {
messageQueue.add(() -> {
Set<Integer> _uids = channelSubscribe.get(channelid);
_uids.forEach(uid -> {
// 自己发的频道消息 不发送给自己
if (x.getSenduserid() == uid) {
return;
}
wsnode.sendMessage(chatService.buildMessageDetail(x), uid).thenAccept(status -> {
// 完成群发消息同步
if (status != 0) {
return;
}
// todo并发场景下会影响拉取离线消息
longCache.setHm("im:channel:" + x.buildChannelid(), uid, System.currentTimeMillis());
});
});
});
});
}
uids.add(userid);
channelSubscribe.put(channelid, uids);
}
@Comment("取消订阅频道")
private void unsubscribeChannel(int userid, String channelid) {
Set<Integer> uids = channelSubscribe.get(channelid);
if (uids == null) {
return;
}
// 先移除用户
uids.remove(userid);
// 当主题订阅没有人在线取消总线订阅
if (uids.isEmpty()) {
channelSubscribe.remove(channelid);
zhub.unsubscribe("im:channel:" + channelid);
return;
}
channelSubscribe.put(channelid, uids);
}
public void subscribeChannel(int userid, String channelid, boolean subscribe) {
Kv kv = Kv.of("userid", userid)
.set("channelid", channelid)
.set("type", subscribe ? "subscribe" : "unsubscribe");
zhub.broadcast("im:channel", kv);
}
public static void main(String[] args) {
long millis = System.currentTimeMillis();
System.out.println(Utils.fmt36(millis));
System.out.println(millis);
System.out.println(Utility.uuid());
}
}

View File

@ -0,0 +1,17 @@
package com.zchd.zim.bean;
import com.zchd.base.BaseBean;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class ImBean extends BaseBean {
private String appid;
private String appsecret;
private String guserid;
}

View File

@ -0,0 +1,17 @@
package com.zchd.zim.bean;
import com.zchd.base.BaseBean;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class SubscribeBean extends BaseBean {
private String imtoken = "";
private String channeltype = "";
private String channelvalue = "";
}

View File

@ -0,0 +1,34 @@
package com.zchd.zim.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
@Setter
@Getter
@Table(comment = "平台信息表")
public class AppInfo {
@Id
@Column(comment = "[平台标识]")
private String appid = "";
@Column(comment = "[平台名称]")
private String appname = "";
@Column(comment = "[校验码]")
private String appsecret = "";
@Column(comment = "[创建时间]")
private long createtime;
@Column(comment = "[修改时间]")
private long updatetime;
@Column(comment = "[状态]10正常, 40停用80删除")
private short status;
}

View File

@ -0,0 +1,44 @@
package com.zchd.zim.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
@Setter
@Getter
@Table(comment = "频道内容表")
public class ChannelMessage {
@Id
@Column(comment = "[消息ID]")
private String messageid;
@Column(comment = "[频道内容]")
private String content = "";
@Column(comment = "[平台ID]")
private String appid;
@Column(comment = "[平台频道类型]")
private String channeltype;
@Column(comment = "[平台频道ID]")
private String channelvalue;
@Column(comment = "[发送人]")
private int senduserid;
@Column(comment = "[创建时间]")
private long createtime;
@Column(comment = "[状态]10正常40发送失败80屏蔽")
private short status;
public String buildChannelid() {
return appid + "-" + channeltype + "-" + channelvalue;
}
}

View File

@ -0,0 +1,63 @@
package com.zchd.zim.entity;
import com.zchd.zim.bean.SubscribeBean;
import lombok.Getter;
import lombok.Setter;
import org.redkale.util.Utility;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
@Setter
@Getter
@Table(comment = "用户组信息表")
public class ChannelUser {
@Id
@Column(comment = "[记录ID]")
private String cuid;
@Column(comment = "[平台ID]")
private String appid = "";
@Column(comment = "[平台频道ID]")
private String channelvalue = "";
@Column(comment = "[平台频道类型]")
private String channeltype = "";
@Column(comment = "[订阅用户]")
private int userid;
@Column(comment = "[创建时间]")
private long createtime;
@Column(comment = "[最后接收时间]")
private long lastaccepttime;
@Column(comment = "[状态]10正常40禁言41踢除")
private short status;
public ChannelUser() {
}
// 频道的订阅唯一KEY
public String buildChannelid() {
return appid + '-' + channeltype + '-' + channelvalue;
}
public static ChannelUser buildChannelUser(int userid, String appid, SubscribeBean bean) {
ChannelUser record = new ChannelUser();
record.userid = userid;
record.appid = appid;
record.channeltype = bean.getChanneltype();
record.channelvalue = bean.getChannelvalue();
record.createtime = System.currentTimeMillis();
record.lastaccepttime = record.getCreatetime();
record.cuid = Utility.uuid();
record.status = 10;
return record;
}
}

View File

@ -0,0 +1,47 @@
package com.zchd.zim.entity;
import com.zchd.base.util.Utils;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
@Setter
@Getter
@Table(comment = "私聊内容表")
public class FriendMessage {
@Id
@Column(comment = "[私聊ID]发送人-时间戳")
private String messageid = "";
@Column(comment = "[私聊内容]")
private String content = "";
@Column(comment = "[发送人]")
private int senduserid;
@Column(comment = "[接收人]")
private int receiveuserid;
@Column(comment = "[创建时间]")
private long createtime;
@Column(comment = "[状态]10已发送20未发送")
private short status;
public static FriendMessage buildFriendMessage(String content, int senduserid, int receiveuserid) {
FriendMessage message = new FriendMessage();
message.content = content;
message.senduserid = senduserid;
message.receiveuserid = receiveuserid;
message.createtime = System.currentTimeMillis();
message.status = 20;
message.messageid = Utils.fmt36(senduserid) + "-" + Utils.fmt36(message.createtime);
return message;
}
}

View File

@ -0,0 +1,50 @@
package com.zchd.zim.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Setter
@Getter
@Table(comment = "IM用户")
public class ImUser implements Serializable {
public static final short STATUS_FREEZE_ACTIVE = 41;//禁言
@Id
@Column(comment = "[用户标识]")
private int userid;
@Column(comment = "[平台自用用户标识]")
private String guserid = "";
@Column(comment = "[关联平台标识]")
private String appid = "";
@Column(comment = "[创建时间]")
private long createtime;
@Column(comment = "[修改时间]")
private long updatetime;
@Column(comment = "[接收时间]")
private long lastaccepttime;
private String imtoken;
@Column(comment = "[状态]10正常40停用80删除")
private short status;
public static ImUser buildImUser(String guserid, String appid) {
ImUser user = new ImUser();
user.guserid = guserid;
user.appid = appid;
user.status = 10;
user.createtime = System.currentTimeMillis();
return user;
}
}