新增验证码模块,调整代码

This commit is contained in:
wenc000 2020-03-06 23:12:45 +08:00
parent 4d0b643f7b
commit 008a14fbf1
20 changed files with 420 additions and 32 deletions

View File

@ -106,7 +106,7 @@
{{# for(var i = 0, item = files[i]; item = files[i++];) {}}
<span class="layui-btn-group">
<a class="layui-btn layui-btn-normal" href="route/file/downloadfile/false/{{item}}">点击下载此文件</a>
<a class="layui-btn layui-btn-danger text-danger" href="javascript:void(0);" lay-form-button data-id="{{item}}" attr="data-name="${field.fieldName}" lay-filter="${field.fieldName}RemoveFile">删除</a>
<a class="layui-btn layui-btn-danger text-danger" href="javascript:void(0);" lay-form-button data-id="{{item}}" data-name="${field.fieldName}" lay-filter="${field.fieldName}RemoveFile">删除</a>
</span>
{{# } }}
{{# } }}
@ -414,7 +414,7 @@
<#elseif field.fieldType == "selectDepartment">
// 初始化${field.fieldExplain}选择部门
function init${field.firstUpperFieldName}SelectDepartment() {
$(document.body).on('click', '#${field.fieldType}SelectDepartment', function() {
$(document.body).on('click', '#${field.fieldName}SelectDepartment', function() {
var name = this.dataset.name;
var selectedNodes = [];
var selectDepartment = $('#'+ name).val().split(',');

View File

@ -63,7 +63,7 @@
<#elseif field.fieldType == "select">
<div class="layui-form-item">
<label class="layui-form-label">${field.fieldExplain}</label>
<div class="layui-input-block layui-form" id="${field.fieldName}SelectTemplateBox"></div>
<div class="layui-input-block layui-form" id="${field.fieldName}SelectTemplateBox" lay-filter="${field.fieldName}SelectTemplateBox"></div>
<script id="${field.fieldName}SelectTemplate" type="text/html">
<select id="${field.fieldName}" name="${field.fieldName}">
<option value="">请选择${field.fieldExplain}</option>
@ -76,7 +76,7 @@
<#elseif field.fieldType == "checkbox">
<div class="layui-form-item" pane>
<label class="layui-form-label">${field.fieldExplain}</label>
<div class="layui-input-block layui-form" id="${field.fieldName}CheckboxTemplateBox"></div>
<div class="layui-input-block layui-form" id="${field.fieldName}CheckboxTemplateBox" lay-filter="${field.fieldName}CheckboxTemplateBox"></div>
<script id="${field.fieldName}CheckboxTemplate" type="text/html">
{{# for(var i = 0, item; item = d[i++];) { }}
<input type="checkbox" name="${field.fieldName}[{{item.dictionaryId}}]" value="{{item.dictionaryId}}" title="{{item.dictionaryName}}">
@ -86,7 +86,7 @@
<#elseif field.fieldType == "radio">
<div class="layui-form-item" pane>
<label class="layui-form-label">${field.fieldExplain}</label>
<div class="layui-input-block" id="${field.fieldName}RadioTemplateBox"></div>
<div class="layui-input-block" id="${field.fieldName}RadioTemplateBox" lay-filter="${field.fieldName}RadioTemplateBox"></div>
<script id="${field.fieldName}RadioTemplate" type="text/html">
{{# for(var i = 0, item; item = d[i++];) { }}
<input type="radio" name="${field.fieldName}" value="{{item.dictionaryId}}" title="{{item.dictionaryName}}">
@ -108,7 +108,7 @@
{{# for(var i = 0, item = files[i]; item = files[i++];) {}}
<span class="layui-btn-group">
<a class="layui-btn layui-btn-normal" href="route/file/downloadfile/false/{{item}}">点击下载此文件</a>
<a class="layui-btn layui-btn-danger text-danger" href="javascript:void(0);" lay-form-button data-id="{{item}}" attr="data-name="${field.fieldName}" lay-filter="${field.fieldName}RemoveFile">删除</a>
<a class="layui-btn layui-btn-danger text-danger" href="javascript:void(0);" lay-form-button data-id="{{item}}" data-name="${field.fieldName}" lay-filter="${field.fieldName}RemoveFile">删除</a>
</span>
{{# } }}
{{# } }}
@ -456,7 +456,7 @@
// 初始化${field.fieldExplain}选择部门
function init${field.firstUpperFieldName}SelectDepartment() {
var showSelectedDepartmentsVal = '';
var selectDepartment = $('#${field.fieldType}').val().split(',');
var selectDepartment = $('#${field.fieldName}').val().split(',');
for(var selectDepartmentIndex = 0, selectDepartmentItem = selectDepartment[selectDepartmentIndex]; selectDepartmentItem = selectDepartment[selectDepartmentIndex++];) {
var departmentInfo = selectDepartmentItem.split('|');
if(showSelectedDepartmentsVal.length > 0) {
@ -464,9 +464,9 @@
}
showSelectedDepartmentsVal += departmentInfo[1];
}
$('#${field.fieldType}SelectDepartment').val(showSelectedDepartmentsVal);
$('#${field.fieldName}SelectDepartment').val(showSelectedDepartmentsVal);
$(document.body).on('click', '#${field.fieldType}SelectDepartment', function() {
$(document.body).on('click', '#${field.fieldName}SelectDepartment', function() {
var name = this.dataset.name;
var selectedNodes = [];
var selectDepartment = $('#'+ name).val().split(',');

View File

@ -130,7 +130,7 @@
<label class="layui-form-label">校验类型 *</label>
<div class="layui-input-block">
<select name="verifyType" lay-filter="verifyType" lay-verify="required">
<option value="none"></option>
<option value="none" selected></option>
<option value="required">不为空</option>
<option value="phone">手机号</option>
<option value="email">邮件</option>

View File

@ -257,6 +257,9 @@
function initData() {
var loadLayerIndex;
top.restAjax.get(top.restAjax.path('api/dynamicconfigtable/dynamicconfigform/getformbyid/{id}', [id]), {}, null, function(code, data) {
initFormItemShowHide(data.fieldType);
initVerifyRegular(data.verifyType);
form.val('dataForm', {
tableName: data.tableName,
fieldName: data.fieldName,
@ -265,7 +268,7 @@
fieldDefault: data.fieldDefault,
dictionaryId: data.dictionaryId,
dictionaryName: data.dictionaryName,
verifyType: data.verifyType,
verifyType: data.verifyType == '' ? 'none' : data.verifyType,
verifyRegular: data.verifyRegular,
fieldSort: data.fieldSort,
listShow: data.listShow.toString(),
@ -279,8 +282,6 @@
});
form.render(null, 'dataForm');
initFormItemShowHide(data.fieldType);
initVerifyRegular(data.verifyType);
}, function(code, data) {
top.dialog.msg(data.msg);
}, function() {

View File

@ -0,0 +1,39 @@
package com.cm.common.plugin.startup;
import com.cm.common.config.properties.OauthClientProperties;
import com.cm.common.config.properties.OauthProperties;
import com.cm.common.plugin.oauth.token.ClientTokenManager;
import com.cm.common.plugin.utils.RestTemplateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: OAuthStartUp
* @Description:
* @Author: WangGeng
* @Date: 2020/3/4 9:14 下午
* @Version: 1.0
**/
@Configuration
public class OAuthStartUp implements ApplicationRunner {
@Autowired
private OauthClientProperties oauthClientProperties;
@Autowired
private OauthProperties oauthProperties;
@Autowired
private RestTemplateUtil restTemplateUtil;
@Override
public void run(ApplicationArguments args) throws Exception {
ClientTokenManager clientTokenManager = ClientTokenManager.getInstance();
clientTokenManager.setRestTemplateUtil(restTemplateUtil);
clientTokenManager.setOauthProperties(oauthProperties);
clientTokenManager.setOauthClientProperties(oauthClientProperties);
}
}

View File

@ -6,18 +6,18 @@ import com.cm.common.constants.ISystemConstant;
import com.cm.common.enums.UploadTypeEnum;
import com.cm.common.exception.base.SystemException;
import com.cm.common.plugin.service.file.IFileService;
import com.cm.common.pojo.dtos.FileDTO;
import com.cm.common.result.ErrorResult;
import com.cm.common.result.SuccessResultData;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
@ -79,6 +79,17 @@ public class FileController extends AbstractController {
return uploadSingle(audio, UploadTypeEnum.AUDIO, params);
}
@ApiOperation(value = "文件列表", notes = "文件列表接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "ids", value = "id列表逗号分隔", paramType = "form")
})
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
@GetMapping("listfilebyfileid")
public List<FileDTO> listFileByFileId(@RequestParam("ids") String ids) {
List<String> idList = Arrays.asList(ids.split(","));
return fileService.listFileByFileId(idList);
}
/**
* 上传文件
*

View File

@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
@ -166,4 +167,12 @@ public interface IFileService {
* @return
*/
String getUploadExcelErrorPath();
/**
* 通过文件ID获取ID列表
* @param idList
* @return
* @throws SearchException
*/
List<FileDTO> listFileByFileId(List<String> idList) throws SearchException;
}

View File

@ -125,6 +125,13 @@ public class FileServiceImpl extends AbstractService implements IFileService {
return getUploadPath(baseUploadPath, UploadTypeEnum.ERROR_EXCEL, null);
}
@Override
public List<FileDTO> listFileByFileId(List<String> idList) throws SearchException {
Map<String, Object> params = getHashMap(1);
params.put("ids", idList);
return fileDao.listFile(params);
}
/**
* 保存文件信息
*

View File

@ -79,6 +79,13 @@
sys_file
WHERE
is_delete = 0
<if test="ids != null and ids.size > 0">
AND
file_id IN
<foreach collection="ids" index="index" open="(" separator="," close=")">
#{ids[${index}]}
</foreach>
</if>
<if test="fileUrl != null and fileUrl != ''">
AND
file_url LIKE concat('%', #{fileUrl}, '%')

View File

@ -27,6 +27,7 @@ public class WechatOfficialAccountTestRouteController {
@GetMapping("index")
public void index(HttpServletResponse response) throws IOException {
response.setHeader("Content-Type", "text/html;charset=utf-8");
Writer writer = response.getWriter();
writer.write("<!DOCTYPE html><html><body><h1>Hello World!!!</h1><br/><br/><a href=\"/usercenter/wechatroute/officialaccount/index2\">点击跳转</a></body></html>");
writer.flush();
@ -35,6 +36,7 @@ public class WechatOfficialAccountTestRouteController {
@GetMapping("index2")
public void index2(HttpServletResponse response) throws IOException {
response.setHeader("Content-Type", "text/html;charset=utf-8");
Writer writer = response.getWriter();
writer.write("<!DOCTYPE html><html><body><h1>Config Success</h1><br/><br/><a href=\"/usercenter/wechatroute/officialaccount/index\">返回</a></body></html>");
writer.flush();

View File

@ -3,12 +3,9 @@ package com.cm.common.wechat.filter;
import com.cm.common.constants.ISystemConstant;
import com.cm.common.wechat.config.pojo.WechatOfficialAccountProperties;
import com.cm.common.wechat.manager.officialaccount.WechatOfficialAccountManager;
import org.apache.shiro.crypto.hash.Sha1Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
@ -39,9 +36,17 @@ public class WechatFilter implements Filter {
private static final String WECHAT_LOGIN_URL = "/**/wechat/sign/login";
/**
* 微信放行接口
* 微信放行
*/
private static final String WECHAT_RELEASE_URL = "/**/wechatrelease/**";
/**
* 微信放行
*/
private static final String WECHAT_API_RELEASE_URL = "/**/wechat/**release";
/**
* 微信路由放行
*/
private static final String WECHAT_ROUTE_RELEASE_URL = "/**/wechatroute/**release";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@ -53,7 +58,11 @@ public class WechatFilter implements Filter {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestUri = request.getRequestURI();
if (antPathMatcher.match(WECHAT_LOGIN_URL, requestUri) || antPathMatcher.match(WECHAT_RELEASE_URL, requestUri)) {
boolean isRelease = antPathMatcher.match(WECHAT_LOGIN_URL, requestUri)
|| antPathMatcher.match(WECHAT_RELEASE_URL, requestUri)
|| antPathMatcher.match(WECHAT_API_RELEASE_URL, requestUri)
|| antPathMatcher.match(WECHAT_ROUTE_RELEASE_URL, requestUri);
if (isRelease) {
filterChain.doFilter(request, response);
return;
}
@ -75,6 +84,13 @@ public class WechatFilter implements Filter {
return;
}
// 判断session是否存在
Object accessToken = request.getSession().getAttribute(ISystemConstant.SESSION_WECHAT_ACCESS_TOKEN);
if (!StringUtils.isEmpty(accessToken)) {
filterChain.doFilter(request, response);
return;
}
// session不存在
// 绑定校验
// 如果参数都存在标识从服务器重定向回页面获取AccessToken后放行
String codeParameter = request.getParameter("code");
@ -85,18 +101,12 @@ public class WechatFilter implements Filter {
} catch (Exception e) {
LOG.error(e.getMessage(), e);
response.setStatus(404);
filterChain.doFilter(request, response);
return;
}
}
// 判断session是否存在
Object accessToken = request.getSession().getAttribute(ISystemConstant.SESSION_WECHAT_ACCESS_TOKEN);
// session 不存在重定向登录
if (StringUtils.isEmpty(accessToken)) {
response.sendRedirect(WechatOfficialAccountManager.getInstance().getAuthorizeUrl(request.getRequestURL().toString()));
filterChain.doFilter(request, response);
return;
}
filterChain.doFilter(request, response);
// session 不存在重定向登录
response.sendRedirect(WechatOfficialAccountManager.getInstance().getAuthorizeUrl(request.getRequestURL().toString()));
}
/**
@ -128,4 +138,4 @@ public class WechatFilter implements Filter {
this.wechatOfficialAccountProperties = wechatOfficialAccountProperties;
}
}
}

View File

@ -46,6 +46,8 @@ public class WechatOfficialAccountManager {
private String accessToken;
private long updateTime = 0L;
private WechatOfficialAccountManager() {}
public static WechatOfficialAccountManager getInstance() {
return wechatOfficialAccountManager;
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cm-cloud</artifactId>
<groupId>com.cm</groupId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>验证码模块</description>
<artifactId>cloud-manager-verification-code</artifactId>
<dependencies>
<dependency>
<groupId>com.cm</groupId>
<artifactId>cloud-common</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,46 @@
package com.cm.manager.verificationcode.controller;
import com.cm.common.constants.ISystemConstant;
import com.cm.common.exception.ParamsException;
import com.cm.common.result.SuccessResult;
import com.cm.common.utils.RegexUtil;
import com.cm.manager.verificationcode.manager.VerificationCodeManager;
import com.cm.manager.verificationcode.service.IVerificationCodeService;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: VerificationCodeController
* @Description: 验证码
* @Author: WangGeng
* @Date: 2020/3/4 7:13 下午
* @Version: 1.0
**/
@Api(tags = ISystemConstant.API_TAGS_SYSTEM_PREFIX + "验证码接口")
@RestController
@RequestMapping(ISystemConstant.API_PREFIX + "/verificationcode")
public class VerificationCodeController {
@Autowired
private IVerificationCodeService verificationCodeService;
@GetMapping("getverificationcode/{type}/{phone}")
public SuccessResult getVerificationCode(@PathVariable("type") String type, @PathVariable("phone") String phone) {
if (!StringUtils.equals(IVerificationCodeService.CUSTOM_TYPE, type)) {
throw new ParamsException("类别错误");
}
if (!RegexUtil.isPhone(phone)) {
throw new ParamsException("手机号格式错误");
}
return verificationCodeService.getVerificationCode(type, phone);
}
}

View File

@ -0,0 +1,86 @@
package com.cm.manager.verificationcode.manager;
import com.cm.common.exception.base.SystemException;
import com.cm.manager.verificationcode.pojo.VerificationCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: VerificationCodeManager
* @Description: 验证码管理
* @Author: WangGeng
* @Date: 2020/3/4 5:34 下午
* @Version: 1.0
**/
public class VerificationCodeManager {
private static final Logger LOG = LoggerFactory.getLogger(VerificationCodeManager.class);
private static final VerificationCodeManager verificationCodeManager = VerificationCodeManagerBuilder.verificationCodeManager;
private Map<String, VerificationCode> verificationCodeMap = new ConcurrentHashMap<>();
/**
* 有效时间60s
*/
public static final long EXPIRE_TIME = 60000L;
private VerificationCodeManager() {
}
public static VerificationCodeManager getInstance() {
return verificationCodeManager;
}
/**
* 设置验证码
*
* @param key
* @param code
*/
public void setVerificationCode(String key, String code) {
VerificationCode verificationCode = new VerificationCode();
verificationCode.setCode(code);
verificationCode.setTime(System.currentTimeMillis());
verificationCodeMap.put(key, verificationCode);
}
/**
* 获取验证码
*
* @param key
* @return
*/
public String getVerificationCode(String key) {
VerificationCode verificationCode = verificationCodeMap.get(key);
if (System.currentTimeMillis() - verificationCode.getTime() <= EXPIRE_TIME) {
verificationCode.getCode();
}
return null;
}
/**
* 删除过期验证码
*/
public void clearExpireTimeCode() {
int size = 0;
long currentTime = System.currentTimeMillis();
for (Map.Entry<String, VerificationCode> kv : verificationCodeMap.entrySet()) {
VerificationCode verificationCode = kv.getValue();
if (currentTime - verificationCode.getTime() > EXPIRE_TIME) {
verificationCodeMap.remove(kv.getKey());
size++;
}
}
LOG.debug("本次清除超时验证码:{}个", size);
}
private static class VerificationCodeManagerBuilder {
public static VerificationCodeManager verificationCodeManager = new VerificationCodeManager();
}
}

View File

@ -0,0 +1,44 @@
package com.cm.manager.verificationcode.pojo;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: VerificationCode
* @Description: 验证码
* @Author: WangGeng
* @Date: 2020/3/4 5:50 下午
* @Version: 1.0
**/
public class VerificationCode {
private String code;
private long time;
public String getCode() {
return code == null ? "" : code.trim();
}
public void setCode(String code) {
this.code = code;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"code\":")
.append("\"").append(code).append("\"");
sb.append(",\"time\":")
.append(time);
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,34 @@
package com.cm.manager.verificationcode.service;
import com.cm.common.result.SuccessResult;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: IVerificationCodeService
* @Description: 验证码
* @Author: WangGeng
* @Date: 2020/3/4 7:44 下午
* @Version: 1.0
**/
public interface IVerificationCodeService {
/**
* 腾讯类别
*/
String TX_TYPE = "tx";
/**
* 自定义类别
*/
String CUSTOM_TYPE = "custom";
/**
* 获取验证码
*
* @param type
* @param phone
* @return
*/
SuccessResult getVerificationCode(String type, String phone);
}

View File

@ -0,0 +1,35 @@
package com.cm.manager.verificationcode.service.impl;
import com.cm.common.base.AbstractService;
import com.cm.common.result.SuccessResult;
import com.cm.manager.verificationcode.manager.VerificationCodeManager;
import com.cm.manager.verificationcode.service.IVerificationCodeService;
import org.springframework.stereotype.Service;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: VerificationCodeServiceImpl
* @Description: 验证码
* @Author: WangGeng
* @Date: 2020/3/4 7:44 下午
* @Version: 1.0
**/
@Service
public class VerificationCodeServiceImpl extends AbstractService implements IVerificationCodeService {
@Override
public SuccessResult getVerificationCode(String type, String phone) {
String currentTimeStr = String.valueOf(System.currentTimeMillis());
String code = currentTimeStr.substring(currentTimeStr.length() - 6);
VerificationCodeManager.getInstance().setVerificationCode(phone, code);
sendCode(type, code);
return new SuccessResult();
}
private void sendCode(String type, String code) {
LOG.debug("发送验证码:{}", code);
}
}

View File

@ -0,0 +1,31 @@
package com.cm.manager.verificationcode.task;
import com.cm.manager.verificationcode.manager.VerificationCodeManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: VerificationCodeTask
* @Description: 验证码任务
* @Author: WangGeng
* @Date: 2020/3/4 7:56 下午
* @Version: 1.0
**/
@Configuration
@EnableScheduling
public class VerificationCodeTask {
/**
* 清除过期验证码任务
*/
@Scheduled(cron = "30 * * * * ?")
public void verificationCodeExpireTimeClearTask() {
VerificationCodeManager.getInstance().clearExpireTimeCode();
}
}

View File

@ -17,6 +17,7 @@
<module>cloud-common-plugin-dynamic</module>
<module>cloud-common-plugin-dictionary</module>
<module>cloud-common-wechat</module>
<module>cloud-manager-verification-code</module>
</modules>
<packaging>pom</packaging>
<description>成迈云</description>