| | |
| | | <artifactId>wx-java-mp-spring-boot-starter</artifactId> |
| | | <version>4.1.0</version> |
| | | </dependency> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>com.aliyuncs</groupId>--> |
| | | <!-- <artifactId>core</artifactId>--> |
| | | <!-- <version>1</version>--> |
| | | <!-- </dependency>--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>com.aliyuncs</groupId>--> |
| | | <!-- <artifactId>dysmsapi</artifactId>--> |
| | | <!-- <version>1</version>--> |
| | | <!-- </dependency>--> |
| | | <dependency> |
| | | <groupId>com.aliyuncs</groupId> |
| | | <artifactId>core</artifactId> |
| | | <version>1</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.aliyuncs</groupId> |
| | | <artifactId>dysmsapi</artifactId> |
| | | <version>1</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | |
| | | <version>2.8.8</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>com.volcengine</groupId> |
| | | <artifactId>volc-sdk-java</artifactId> |
| | | <version>1.0.105</version> |
| | | </dependency> |
| | | |
| | | |
| | | |
| | | |
| | |
| | | // 微信支付暂时测试 |
| | | .antMatchers("/v2/wechat/**").permitAll() |
| | | |
| | | // 短信 |
| | | .antMatchers("/v2/tos/sms/**").permitAll() |
| | | |
| | | .antMatchers("/api/**").authenticated();//配置访问控制,必须认证过后才可以访问 |
| | | |
| | | } |
文件名从 src/main/java/com/mzl/flower/config/TosClientConfig.java 修改 |
| | |
| | | package com.mzl.flower.config; |
| | | package com.mzl.flower.config.oss; |
| | | |
| | | import com.volcengine.tos.TOSV2; |
| | | import com.volcengine.tos.TOSV2ClientBuilder; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | |
| | | public class TosClientConfig { |
| | | |
| | | @Resource |
| | | private TosProperties properties; |
| | | private TosOssProperties properties; |
| | | |
| | | @Bean |
| | | public TOSV2 tosClient() { |
文件名从 src/main/java/com/mzl/flower/config/TosProperties.java 修改 |
| | |
| | | package com.mzl.flower.config; |
| | | package com.mzl.flower.config.oss; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | |
| | | @Data |
| | | @Component |
| | | @ConfigurationProperties(prefix = "tos-oss") |
| | | public class TosProperties { |
| | | public class TosOssProperties { |
| | | |
| | | private String endpoint; |
| | | private String region; |
对比新文件 |
| | |
| | | package com.mzl.flower.config.sms; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | @Data |
| | | @Component |
| | | @ConfigurationProperties(prefix = "tos-sms") |
| | | public class TosSmsProperties { |
| | | |
| | | private String keyid; |
| | | private String keysecret; |
| | | private String smsAccount; |
| | | private String templateId; |
| | | private String sign; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.mzl.flower.config.sms; |
| | | |
| | | import com.mzl.flower.service.sms.TosSmsService; |
| | | import com.volcengine.service.sms.SmsService; |
| | | import com.volcengine.service.sms.SmsServiceInfoConfig; |
| | | import com.volcengine.service.sms.impl.SmsServiceImpl; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | @Configuration |
| | | public class TosSmsServiceConfig { |
| | | |
| | | @Resource |
| | | private TosSmsProperties tosSmsProperties; |
| | | |
| | | @Bean(name = "volcSmsService") |
| | | public SmsService smsService() { |
| | | return SmsServiceImpl.getInstance(new SmsServiceInfoConfig(tosSmsProperties.getKeyid(), tosSmsProperties.getKeysecret())); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.aliyun.oss.OSSClientBuilder; |
| | | import com.aliyun.oss.OSSException; |
| | | import com.aliyun.oss.model.GetObjectRequest; |
| | | import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | |
| | | String templateCode = smsTemplateDO.getCode(); |
| | | for (SmsTaskDetailDO detail : smsTaskDetailDOList) { |
| | | try { |
| | | // SendSmsResponse sendSmsResponse = SmsUtil.sendSms(detail.getPhone(), templateCode, null); |
| | | // if("OK".equals(sendSmsResponse.getCode())){ |
| | | // detail.setResult(Constants.SMS_SEND_RESULT.success.name()); |
| | | // }else{ |
| | | // detail.setFailReason(sendSmsResponse.getMessage()); |
| | | // detail.setResult(Constants.SMS_SEND_RESULT.failure.name()); |
| | | // } |
| | | // detail.setResponseResult(sendSmsResponse.toString()); |
| | | SendSmsResponse sendSmsResponse = SmsUtil.sendSms(detail.getPhone(), templateCode, null); |
| | | if("OK".equals(sendSmsResponse.getCode())){ |
| | | detail.setResult(Constants.SMS_SEND_RESULT.success.name()); |
| | | }else{ |
| | | detail.setFailReason(sendSmsResponse.getMessage()); |
| | | detail.setResult(Constants.SMS_SEND_RESULT.failure.name()); |
| | | } |
| | | detail.setResponseResult(sendSmsResponse.toString()); |
| | | } catch (Exception e) { |
| | | detail.setResult(Constants.SMS_SEND_RESULT.failure.name()); |
| | | System.err.println("Failed to send SMS to " + detail.getPhone() + ": " + e.getMessage()); |
对比新文件 |
| | |
| | | package com.mzl.flower.service.impl.sms; |
| | | |
| | | import com.mzl.flower.config.sms.TosSmsProperties; |
| | | import com.mzl.flower.service.sms.TosSmsService; |
| | | import com.volcengine.model.request.SmsSendRequest; |
| | | import com.volcengine.model.response.SmsSendResponse; |
| | | import com.volcengine.service.sms.SmsService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | public class TosSmsServiceImpl implements TosSmsService { |
| | | |
| | | @Autowired |
| | | private TosSmsProperties tosSmsProperties; |
| | | |
| | | @Autowired |
| | | @Qualifier("volcSmsService") |
| | | private SmsService smsService; |
| | | |
| | | @Override |
| | | public SmsSendResponse sendSms(SmsSendRequest request) { |
| | | SmsSendResponse response = null; |
| | | try { |
| | | response = smsService.sendV2(request); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | return response; |
| | | } |
| | | |
| | | @Override |
| | | public SmsSendResponse sendSms(String phonenum, Map<String, String> params) { |
| | | SmsSendRequest req = new SmsSendRequest(); |
| | | req.setPhoneNumbers(phonenum); |
| | | req.setSmsAccount(tosSmsProperties.getSmsAccount()); |
| | | req.setTemplateId(tosSmsProperties.getTemplateId()); |
| | | req.setSign(tosSmsProperties.getSign()); |
| | | req.setTemplateParamByMap(params); |
| | | try { |
| | | SmsSendResponse smsSendResponse = smsService.sendV2(req); |
| | | return smsSendResponse; |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public SmsSendResponse sendSms(String phonenum, Map<String, String> params, String smsAccount, String templateId, String sign) { |
| | | SmsSendRequest req = new SmsSendRequest(); |
| | | req.setPhoneNumbers(phonenum); |
| | | req.setSmsAccount(smsAccount); |
| | | req.setTemplateId(templateId); |
| | | req.setSign(sign); |
| | | req.setTemplateParamByMap(params); |
| | | try { |
| | | SmsSendResponse smsSendResponse = smsService.sendV2(req); |
| | | return smsSendResponse; |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | } |
文件名从 src/main/java/com/mzl/flower/service/TosService.java 修改 |
| | |
| | | package com.mzl.flower.service; |
| | | package com.mzl.flower.service.oss; |
| | | |
| | | |
| | | import com.mzl.flower.config.TosProperties; |
| | | import com.mzl.flower.config.oss.TosOssProperties; |
| | | import com.mzl.flower.config.exception.ValidationException; |
| | | import com.mzl.flower.utils.UUIDGenerator; |
| | | import com.volcengine.tos.TOSV2; |
| | |
| | | import com.volcengine.tos.model.object.PutObjectInput; |
| | | import com.volcengine.tos.model.object.PutObjectOutput; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | |
| | | @Service |
| | | public class TosService { |
| | | public class TosOssService { |
| | | |
| | | @Autowired |
| | | private TOSV2 tosClient; |
| | | |
| | | @Autowired |
| | | private TosProperties tosProperties; |
| | | private TosOssProperties tosOssProperties; |
| | | |
| | | private String upladPre="https://"; |
| | | |
| | | |
| | | |
| | | public TosService(TOSV2 tosClient) { |
| | | public TosOssService(TOSV2 tosClient) { |
| | | this.tosClient = tosClient; |
| | | } |
| | | |
| | |
| | | public String uploadFile(String objectKey, String filePath) throws IOException { |
| | | try { |
| | | PutObjectFromFileInput input = new PutObjectFromFileInput() |
| | | .setBucket(tosProperties.getBucketname()).setKey(objectKey).setFilePath(filePath); |
| | | .setBucket(tosOssProperties.getBucketname()).setKey(objectKey).setFilePath(filePath); |
| | | |
| | | PutObjectFromFileOutput output = tosClient.putObjectFromFile(input); |
| | | return "Upload success! ETag: " + output.getEtag(); |
| | |
| | | filename = uuid + filename; |
| | | String dir = uuid.substring(0, 2); |
| | | filename = dir + "/" + filename; |
| | | PutObjectInput putObjectInput = new PutObjectInput().setBucket(tosProperties.getBucketname()).setKey(filename).setContent(inputStream); |
| | | PutObjectInput putObjectInput = new PutObjectInput().setBucket(tosOssProperties.getBucketname()).setKey(filename).setContent(inputStream); |
| | | PutObjectOutput output = tosClient.putObject(putObjectInput); |
| | | // System.out.println("putObject succeed, object's etag is " + output.getEtag()); |
| | | // System.out.println("putObject succeed, object's crc64 is " + output.getHashCrc64ecma()); |
| | | // https://edu-mys.oss-cn-chengdu.aliyuncs.com/yy.JPG |
| | | String url = upladPre+tosProperties.getBucketname() + "." + tosProperties.getEndpoint() + "/" + filename; |
| | | String url = upladPre+ tosOssProperties.getBucketname() + "." + tosOssProperties.getEndpoint() + "/" + filename; |
| | | return url; |
| | | } catch (TosClientException | TosServerException e) { |
| | | throw new ValidationException("上传到云服务器发生异常"); |
| | |
| | | import com.mzl.flower.config.exception.ValidationException; |
| | | import com.mzl.flower.constant.Constants; |
| | | import com.mzl.flower.dto.request.SmsSendDTO; |
| | | import com.mzl.flower.service.sms.TosSmsService; |
| | | import com.mzl.flower.utils.SmsUtil; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | |
| | | private final SmsProperties smsProperties; |
| | | |
| | | private final StringCacheClient stringCacheClient; |
| | | |
| | | @Autowired |
| | | private TosSmsService tosSmsService; |
| | | |
| | | |
| | | public SmsService(SmsProperties smsProperties, StringCacheClient stringCacheClient) { |
| | | this.smsProperties = smsProperties; |
| | |
| | | if(existsCode(dto.getTel())){ |
| | | throw new ValidationException("短信验证码已发送,请勿频繁发送"); |
| | | } |
| | | // String smsCode = generateSmsCode(); |
| | | String smsCode ="888888"; |
| | | String smsCode = generateSmsCode(); |
| | | // String smsCode ="888888"; |
| | | String key; |
| | | if(Constants.USER_TYPE.admin.name().equals(dto.getUserType())){ |
| | | key = SMS_CODE_KEY + SEPARATOR + Constants.USER_TYPE.admin.name() + SEPARATOR + dto.getTel(); |
| | |
| | | |
| | | Map<String, String> paramMap = new HashMap<>(); |
| | | paramMap.put("code", smsCode); |
| | | // try { |
| | | // //暂时不实际发送验证码 |
| | | try { |
| | | //暂时不实际发送验证码 |
| | | // SmsUtil.sendSms(dto.getTel(),smsProperties.getVerificationCode(),paramMap); |
| | | stringCacheClient.set(SMS_CODE_KEY + SEPARATOR + SEPARATOR + dto.getTel(),smsCode,60); |
| | | stringCacheClient.set(key,smsCode,600); |
| | | // } catch (ClientException e) { |
| | | // throw new RuntimeException("短信发送失败"); |
| | | // } |
| | | //todo 发送短信 |
| | | tosSmsService.sendSms(dto.getTel(),paramMap); |
| | | stringCacheClient.set(SMS_CODE_KEY + SEPARATOR + SEPARATOR + dto.getTel(),smsCode,60); |
| | | stringCacheClient.set(key,smsCode,600); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("短信发送失败"); |
| | | } |
| | | } |
| | | |
| | | private boolean existsCode(String tel) { |
对比新文件 |
| | |
| | | package com.mzl.flower.service.sms; |
| | | |
| | | import com.volcengine.model.request.SmsSendRequest; |
| | | import com.volcengine.model.response.SmsSendResponse; |
| | | |
| | | import java.util.Map; |
| | | |
| | | public interface TosSmsService { |
| | | |
| | | /** |
| | | * 根据封装的request来发送请求 |
| | | * @param request |
| | | * @return |
| | | */ |
| | | public SmsSendResponse sendSms(SmsSendRequest request); |
| | | |
| | | /** |
| | | * 使用默认配置的账户,模板id,签名 |
| | | * @param phonenum |
| | | * @param params |
| | | * @return |
| | | */ |
| | | |
| | | public SmsSendResponse sendSms(String phonenum, Map<String,String> params); |
| | | |
| | | |
| | | |
| | | public SmsSendResponse sendSms(String phonenum, Map<String,String> params,String smsAccount,String templateId,String sign); |
| | | |
| | | |
| | | } |
| | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.aliyuncs.DefaultAcsClient; |
| | | import com.aliyuncs.IAcsClient; |
| | | import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; |
| | | import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; |
| | | import com.aliyuncs.exceptions.ClientException; |
| | | import com.aliyuncs.profile.DefaultProfile; |
| | | import com.aliyuncs.profile.IClientProfile; |
| | |
| | | |
| | | static final String connectTime = "30000"; |
| | | static final String readTime = "30000"; |
| | | static final String keyId = "LTAI5tFGHa9bwhuEDKH6YPnc"; |
| | | static final String keyId = "LTAI5tRr7uLjPPxzGMJPH6fz"; |
| | | static final String keySecret = "GiOamDfkVP4TWiNnuSptyGQLuMRBMG"; |
| | | |
| | | static final String keySecret = "BrY0BM4pvDXhVKOMLsXzgzlhVe1keQ"; |
| | | static final String signName = "云南花满芫花卉"; |
| | | static final String signName = "苏州云游四方信息科技"; |
| | | |
| | | public static SendSmsResponse sendSms(final String tel, final String templateCode, Object paramMap) throws ClientException { |
| | | return sendSms(tel, templateCode, JSON.toJSONString(paramMap)); |
| | | } |
| | | |
| | | public static SendSmsResponse sendSms(final String tel, final String templateCode, final String templateParam) throws ClientException { |
| | | return sendSms(tel, templateCode, templateParam, null); |
| | | } |
| | | |
| | | public static SendSmsResponse sendSms(final String tel, final String templateCode, final String templateParam, final String outId) throws ClientException { |
| | | log.info("Send SMS [mobile no:" + tel + " templateCode:" + templateCode + " templateParam:" + templateParam + "]"); |
| | | System.setProperty("sun.net.client.defaultConnectTimeout", connectTime); |
| | | System.setProperty("sun.net.client.defaultReadTimeout", readTime); |
| | | IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", keyId, keySecret); |
| | | DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain); |
| | | IAcsClient acsClient = new DefaultAcsClient(profile); |
| | | SendSmsRequest request = new SendSmsRequest(); |
| | | request.setPhoneNumbers(tel); |
| | | request.setSignName(signName); |
| | | request.setTemplateCode(templateCode); |
| | | request.setTemplateParam(templateParam); |
| | | request.setOutId(outId); |
| | | SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); |
| | | log.info("Send SMS [mobile no:" + tel + " templateCode:" + templateCode + " templateParam:" + templateParam + "] result: " + sendSmsResponse.getCode() + " " + sendSmsResponse.getMessage()); |
| | | return sendSmsResponse; |
| | | } |
| | | |
| | | } |
| | |
| | | import com.mzl.flower.base.ReturnDataDTO; |
| | | import com.mzl.flower.config.exception.ValidationException; |
| | | import com.mzl.flower.config.security.SecurityUtils; |
| | | import com.mzl.flower.constant.Constants; |
| | | import com.mzl.flower.dto.request.supplier.*; |
| | | import com.mzl.flower.dto.response.supplier.SupplierDTO; |
| | | import com.mzl.flower.entity.customer.Customer; |
| | | import com.mzl.flower.service.TosService; |
| | | import com.mzl.flower.service.oss.TosOssService; |
| | | import com.mzl.flower.service.customer.CustomerService; |
| | | import com.mzl.flower.service.supplier.SupplierService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.validation.annotation.Validated; |
| | |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | import java.util.UUID; |
| | | |
| | | import static com.google.common.io.Files.getFileExtension; |
| | | import static sun.font.CreatedFontTracker.MAX_FILE_SIZE; |
| | | |
| | | @RestController |
| | |
| | | private CustomerService customerService; |
| | | |
| | | @Autowired |
| | | private TosService tosService; |
| | | private TosOssService tosOssService; |
| | | |
| | | public SupplierController(SupplierService supplierService) { |
| | | this.supplierService = supplierService; |
| | |
| | | String newFileName = originalFilename + (extension != null ? extension : ""); |
| | | |
| | | // 4. 上传到 OSS |
| | | avatarUrl = tosService.uploadFile( avatar.getInputStream(),newFileName); |
| | | avatarUrl = tosOssService.uploadFile( avatar.getInputStream(),newFileName); |
| | | } |
| | | |
| | | // 5. 更新用户信息 |
对比新文件 |
| | |
| | | package com.mzl.flower.web.v2.sms; |
| | | |
| | | import com.mzl.flower.base.BaseController; |
| | | import com.mzl.flower.base.R; |
| | | import com.mzl.flower.base.ReturnDataDTO; |
| | | import com.mzl.flower.config.sms.TosSmsProperties; |
| | | import com.mzl.flower.dto.request.sms.SmsTaskDTO; |
| | | import com.mzl.flower.service.sms.TosSmsService; |
| | | import com.volcengine.model.request.SmsSendRequest; |
| | | import com.volcengine.model.response.SmsSendResponse; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | @Api(value = "火山短信", tags = "火山短信") |
| | | @RestController |
| | | @RequestMapping("/v2/tos/sms") |
| | | public class TosSmsController extends BaseController { |
| | | |
| | | @Autowired |
| | | private TosSmsService tosSmsService; |
| | | |
| | | @Autowired |
| | | private TosSmsProperties tosSmsProperties; |
| | | |
| | | private String template=""; |
| | | |
| | | @PostMapping(value = "/send") |
| | | @ApiOperation(value = "发送短信任务", httpMethod = "POST") |
| | | public ResponseEntity<ReturnDataDTO> send() throws IOException { |
| | | SmsSendRequest req = new SmsSendRequest(); |
| | | req.setPhoneNumbers("17768997336"); |
| | | req.setSmsAccount(tosSmsProperties.getSmsAccount()); |
| | | req.setTemplateId(tosSmsProperties.getTemplateId()); |
| | | req.setSign(tosSmsProperties.getSign()); |
| | | |
| | | Map<String,String> param = new HashMap<>(); |
| | | param.put("code","123456"); |
| | | req.setTemplateParamByMap(param); |
| | | SmsSendResponse smsSendResponse = tosSmsService.sendSms(req); |
| | | return returnData(R.SUCCESS.getCode(), smsSendResponse); |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | bucketname: smart-manager-new |
| | | # bucketname: zhixinguanjia |
| | | |
| | | tos-sms: |
| | | keyid: AKLTNTFmOTFkMDg3ZDRmNGZkMmFkOTBmMTcyZmM4N2MyMmY |
| | | keysecret: TW1FelpUY3lOalkxWWpjMk5HWTNOV0ZtWVdWa1lURTNNRE13WVdJMVkySQ== |
| | | smsAccount: 834e37c1 |
| | | templateId: ST_83652388 |
| | | sign: 智信管家 |
| | | |
| | | |
| | | wx: |
| | | miniapp: |
| | | appid: wx1441324401626290 #小程序appId 花店端 |
| | |
| | | |
| | | |
| | | sms: |
| | | verificationCode: SMS_301300012 #验证码通用模版 |
| | | verificationCode: SMS_317055406 #验证码通用模版 |
| | | |
| | | map: |
| | | tengxun: |