(14)go-micro微服务服务层Handle开发

目录
  • 一 Handle层开发功能说明
      • 需要完成的服务开发功能:
      • 从哪找需要开发的功能
  • 二 代码编写
  • 三 最后

一 Handle层开发功能说明

需要完成的服务开发功能:

  1. 登录
  2. 注册
  3. 查询用户信息
  4. 修改信息
  5. 发送注册邮件
  6. 发送重置密码邮件
  7. 重置密码
  8. 获取权限
  9. 修改权限
  10. 退出账号
  11. 删除账号
  12. 禁用账号
  13. 启用账号

从哪找需要开发的功能

  • 找到proto/account/account.pb.micro.go文件,点击结构,点击AccountService
  • 此部分显示的服务功能需要我们重写完成

image.png

二 代码编写

  • 在handle/account.go文件中,先把原来的内容删掉(删掉函数即可)

  • 编写服务功能:

package handler

import (
   "account/common/mail"
   "account/common/snow_flake"
   "account/common/token"
   "account/common/utils"
   "account/domain/model"
   "account/domain/service"
   . "account/proto/account"
   "context"
   "errors"
   "fmt"
   "time"
)

type Account struct {
   AccountService service.IUserService
}

// Login 登录
func (a *Account) Login(ctx context.Context, req *LoginRequest, rsp *LoginResponse) error {
   userInfo, err := a.AccountService.FindUserByName(req.Username)
   isOk, err := a.AccountService.CheckPwd(req.Username, req.Password)
   if err != nil {
      return err
   }
   token2, err := token.GenToken(req.Username)
   if err != nil {
      return err
   }
   token.SetToken(req.Username, token2)
   rsp.IsSuccess = isOk
   rsp.Token = token2
   rsp.UserId = userInfo.UserID
   return nil
}

// Register 注册
func (a *Account) Register(ctx context.Context, req *RegisterRequest, rsp *RegisterResponse) error {
   var sf snow_flake.Snowflake
   userId := sf.NextVal()
   _, err := mail.CheckMail(req.RegisterRequest.Email, req.Code)
   if err != nil {
      return err
   }
   nowTime := time.Now()
   userRegister := &model.User{
      UserID:     userId,
      UserName:   req.RegisterRequest.Username,
      FirstName:  req.RegisterRequest.FirstName,
      LastName:   req.RegisterRequest.LastName,
      PassWord:   req.RegisterRequest.Password,
      Permission: 0,
      CreateDate: nowTime,
      UpdateDate: nowTime,
      IsActive:   1,
      Email:      req.RegisterRequest.Email,
   }
   _, err = a.AccountService.AddUser(userRegister)
   if err != nil {
      return err
   }
   mail.DelMail(req.RegisterRequest.Email)
   rsp.IsSuccess = true
   rsp.UserId = userId
   return nil
}

// GetUserInfo 查询用户信息
func (a *Account) GetUserInfo(ctx context.Context, req *UserIdRequest, rsp *UserInfoResponse) error {
   userInfo, err := a.AccountService.FindUserByID(req.UserId)
   if err != nil {
      return err
   }
   rsp = utils.UserForResponse(rsp, userInfo)
   fmt.Println(rsp)
   return nil
}

// UpdateUserInfo 修改信息
func (a *Account) UpdateUserInfo(ctx context.Context, req *UserInfoRequest, rsp *Response) error {
   var user *model.User
   err := utils.SwapTo(req, user)
   if err != nil {
      return err
   }
   isChangePwd := false
   if req.UserInfo.Password != "" {
      isChangePwd = true
   }
   if err = a.AccountService.UpdateUser(user, isChangePwd); err != nil {
      return err
   }
   rsp.Message = "修改信息成功"
   return nil
}

// SendRegisterMail 发送注册邮件
func (a *Account) SendRegisterMail(ctx context.Context, req *SendMailRequest, rsp *SendMailResponse) error {
   code, err := mail.SendRegisterMail(req.Email)
   if err != nil {
      return err
   }
   mail.SetMail(req.Email, code)
   rsp.Msg = "邮件发送成功"
   rsp.Code = code
   return nil
}

// SendResetPwdMail 发送重置密码邮件
func (a *Account) SendResetPwdMail(ctx context.Context, req *SendMailRequest, rsp *SendMailResponse) error {
   email := req.Email
   code, err := mail.SendResetPwdMail(email)
   if err != nil {
      return err
   }
   mail.SetMail(email, code)
   rsp.Msg = "邮件发送成功"
   return nil
}

// ResetPwd 重置密码
func (a *Account) ResetPwd(ctx context.Context, req *ResetPwdRequest, rsp *Response) error {
   userInfo, err := a.AccountService.FindUserByID(req.UserId)
   if err != nil {
      return err
   }
   code, err := mail.GetMail(userInfo.Email)
   if err != nil {
      return err
   }
   if code != req.Code {
      return errors.New("验证码错误")
   }
   if err := a.AccountService.ResetPwd(req.UserId, req.Password); err != nil {
      return err
   }
   rsp.Message = "重置密码成功"
   return nil
}

// GetUserPermission 获取权限
func (a *Account) GetUserPermission(ctx context.Context, req *UserIdRequest, rsp *GetPermissionResponse) error {
   permission, err := a.AccountService.GetPermission(req.UserId)
   if err != nil {
      return err
   }
   rsp.Permission = permission
   return nil
}

// UpdateUserPermission 修改权限
func (a *Account) UpdateUserPermission(ctx context.Context, req *UpdatePermissionRequest, rsp *Response) error {
   if err := a.AccountService.UpdatePermission(req.UserId, req.Permission); err != nil {
      return err
   }
   rsp.Message = "修改权限成功"
   return nil
}

// Logout 退出账号
func (a *Account) Logout(ctx context.Context, req *UserIdRequest, rsp *Response) error {
   userInfo, err := a.AccountService.FindUserByID(req.UserId)
   if err != nil {
      return err
   }
   _, err = token.GenToken(userInfo.UserName)
   if err != nil {
      return errors.New("账号未登录!")
   }
   token.DelToken(userInfo.UserName)
   rsp.Message = "退出账号成功"
   return nil
}

// DelUser 删除账号
func (a *Account) DelUser(ctx context.Context, req *UserIdRequest, rsp *Response) error {
   if err := a.AccountService.DeleteUser(req.UserId); err != nil {
      return err
   }
   rsp.Message = "账号删除成功"
   return nil
}

// DisableUser 禁用账号
func (a *Account) DisableUser(ctx context.Context, req *UserIdRequest, rsp *Response) error {
   if err := a.AccountService.DisableUser(req.UserId); err != nil {
      return err
   }
   rsp.Message = "账号禁用成功"
   return nil
}

// EnableUser 启用账号
func (a *Account) EnableUser(ctx context.Context, req *UserIdRequest, rsp *Response) error {
   if err := a.AccountService.EnableUser(req.UserId); err != nil {
      return err
   }
   rsp.Message = "账号启用成功"
   return nil
}
  • 至此,handle层开发完毕

三 最后

  • 至此,go-micro微服务项目服务层Handle开发工作就正式完成。

  • 接下来就开始main.go的代码编写了,希望大家关注博主和关注专栏,第一时间获取最新内容,每篇博客都干货满满。

欢迎大家加入 夏沫の梦的学习交流群 进行学习交流经验,点击 夏沫の梦学习交流

本文转载于网络 如有侵权请联系删除

相关文章

  • MySQL 多实例安装

    在生产中有时候会遇到一台主机要运行多个MySQL服务器----叫做多实例下面演示多实例的安装首先我们规划下基于二进制安装 1、创建mysql用户useradd-r-s/sbin/nologin-d/app/mysql-mmysql # #2、解压下载的二进制mariadb包tarxvfmariadb-10.2.16-linux-x86_64.tar.gz-C/usr/local/ # #3、创建软连接cd/usr/local/ # ln-smariadb-10.2.16-linux-x86_64/mysql #4、修改权限chown-Rroot.mysql/usr/local/mysql/ # #5、环境变量(少了这步也可)vim/etc/profile.d/mysql.sh # ./etc/profile.d/mysql.sh #如果你已经二进制安装了mariadb包,可以从以下开始指定数据库位置并初始化数据库 1、创建规划图上的目录mkdir/mysqldb/{3306,3307,3308}/{etc,bin,data,pid,socket,log}-pv # #2、更改主目录my

  • 基于量子案例的推理 (qCBR)(cs.AI)

    基于案例的推理(CBR)是一种人工智能解决问题的方法,具有良好的成功记录。本文建议使用量子计算来改进CBR定义的一些关键过程,从而形成基于量子案例的推理(qCBR)范式。重点在于设计和实施基于变分原理的qCBR,该变化原则在平均准确性、可扩展性和对重叠的耐受性方面提高了其经典对应值。以社会工作者问题为例,作为重叠的组合优化问题样本,对提出的qCBR与经典CBR的对比研究正在进行。该算法的量子可行性以文档为模型,并在IBMQ计算机上进行测试,并在Qibo框架上进行实验。原文题目:quantumCase-BasedReasoning(qCBR)原文:Case-BasedReasoning(CBR)isanartificialintelligenceapproachtoproblem-solvingwithagoodrecordofsuccess.ThisarticleproposesusingQuantumComputingtoimprovesomeofthekeyprocessesofCBRdefiningsoaQuantumCase-BasedReasoning(qCBR)paradi

  • Android 安卓去除APP的广告几种方法

    Android软件汉化/精简/去广告教程【android去广告教程】还你一个清新的世界~Android去广告教程Android软件去广告方法总结上面这些方法对一般用户都太复杂,都需要这个工具,那个工具,且耗时耗力。但本文提出的方法简单有效,适合一般人使用,可以摆脱Android的绝大部分广告。目录一、Android安卓去广告四大金刚1、hosts法去大部分广告2、LuckyPatcher去软件广告3、去除Android通知栏广告4、Android去广告终极大法–关闭数据二、总结一、Android安卓去广告四大金刚1、hosts法去大部分广告这个方法放在第一位,是因为这个方法最有效,能够阻挡掉90%的安卓广告。这个去广告的方法并不是直接往hosts文件添加要屏蔽的网址,而是通过一个软件AdAway这个软件来进行。非常简单。类似的使用可以参考屏蔽视频广告,让你看土豆、优酷、奇艺视频更自由自在。这个软件有点类似Firefox的屏蔽广告插件AdblockPlus。下载文件就可以了,会自动将屏蔽的网址合并到hosts文件。2、LuckyPatcher去软件广告LuckyPatcher是一款破解软

  • springboot2.x RedisCacheManager变化

    由于最近在学着使用redis做缓存,使用的是springboot2.x来搭建的项目。  看了看网上的一些教程,但是大多数教程都是基于1.x的版本来讲解的,但是springboot2.x之后发生了一些变动,网上想找一些资料不太容易。 springboot配置缓存过期时间,网上大部分资料是使用ReidsCacheManager来进行自定义的配置  以下是大部分网上的代码(这也是基于springboot1.x的版本可以使用的)@Bean publicCacheManagercacheManager(RedisTemplateredisTemplate){ RedisCacheManagercacheManager=newRedisCacheManager(redisTemplate); cacheManager.setDefaultExpiration(60); Map<String,Long>expiresMap=newHashMap<>(); expiresMap.put("Product",5L); cacheManager.setExpi

  • Navicat实用功能:数据备份与结构同步

    当我们要对数据库做有风险的操作时需要对数据库备份,每次上线项目时,线上与线下数据库结构总会有不一致的情况,本文将讲解如何利用Navicat来方便的解决这两个问题。NavicatNavicat是一套快速、可靠的数据库管理工具,专为简化数据库的管理及降低系统管理成本而设。它的设计符合数据库管理员、开发人员及中小企业的需要。Navicat是以直觉化的图形用户界面而建的,让你可以以安全并且简单的方式创建、组织、访问并共用信息。注意:本教程采用的时Navicat12版本,下载地址:https://www.navicat.com.cn/download/navicat-premium使用数据库介绍现在数据库中有两个数据库,mall-test表示测试环境数据库,mall-prod表示线上环境数据库。数据备份现在我们先对mall-test数据库备份,备份完成后,删除商品表的数据,然后利用备份进行数据还原。mall-test中的数据概览目前数据库中只有商品模块的数据库表,pms_product表中有一定的数据。进行数据备份先点击顶部工具栏的备份图标,再点击新建备份按钮点击开始按钮开始备份备份完成后会生成

  • 创建线程的几种方式

    说道线程,肯定会想到使用java.lang.Thread.java这个类那么创建线程也主要有2种方式第一种方式:然后在调用处,执行start方法即可:第二种方式实现Runnable接口:同样在执行的地方直接生命这个MyRunnable,再直接丢进线程start即可:这两种方式都可以用匿名类的方式来实现,但是我并不推荐;另外使用Thread本身来实现线程还是用Runnable来做,我推荐后者,因为相对来说会比较方便,直接往线程中一扔即可,如果使用spring的线程执行器也是同样的道理,往执行器中丢入这个runnable即可需要注意的是,执行线程的时候可以使用start()方法或者run()方法,虽然使用run会达到同样的效果,但是run是在主线程中使用的,也就是使用你当前的方法内线程,而不是另起一个线程,这样就达不到异步的效果,所以务必使用start()

  • Java Jar包的压缩、解压使用指南

    什么是jar包JAR(JavaArchive)是Java的归档文件,它是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件。如何打/解包使用jdk/bin/jar.exe工具,配置完环境变量后直接使得jar命令即可。jar命令格式jar{ctxuf}[vme0Mi][-C目录]文件名...{ctxu},这四个参数必须选选其一。[vfme0Mi],这几个是可选参数,文件名也是必须的。参数说明-c创建一个jar包-t显示jar中的内容列表-x解压jar包-u添加文件到jar包中-f指定jar包的文件名-v输出详细报告-m指定MANIFEST.MF文件-0生成jar包时不压缩内容-M不生成清单文件MANIFEST.MF-i为指定的jar文件创建索引文件-C可在相应的目录下执行命令关于MANIFEST.MF定义:https://baike.baidu.com/item/MANIFEST.MF演示往jar包添加文件jarufxxx.jarBOOT-INF/classes/application.yml解压jar包jar-xvfxxx.jar打jar包,不生成清单文件,不压缩jar-cv

  • httpd启动管理脚本

    #!/bin/bash #chkconfig:23451090 #description:httpdservice httpd="/usr/local/apache2/bin/apachectl" functionhttpd_start(){ /usr/local/apache2/bin/apachectlstart } functionhttpd_stop(){ /usr/local/apache2/bin/apachectlstop } case$1in start) httpd_start ;; stop) httpd_stop ;; restart) httpd_stop sleep2 httpd_start ;; test) $httpd-t#检测httpd配置文件语法问题 ;; *) echo“用法:$0start|stop|restart|test” ;; esac 复制

  • 微信小程序集成vant

       微信小程序集成vant,大概的过程先通过npm安装vant包->微信小程序设置npm环境变量->将npm中的vant包导成miniprogram_npm    开发环境macOS,微信小程序模版【支持腾讯云】 安装vant包 cdminiprogram #通过npm安装 npmi@vant/weapp-S--production 复制 配置微信小程序的样式,去除miniprogram/app.json中"style":"v2" 配置项目config中的packageJsonPath和miniprogramNpmDistDir packageJsonPath->npm的package.json文件的位置 miniprogramNpmDistDir->生成miniprogramNpm的文件目录位置 //下面供参考,开发中按照实际的位置填写 "packNpmRelationList":[ { "packageJsonPath":"./miniprogram/package.json", "miniprogramNpmDistDir":"./min

  • typescript-plugin-css-modules不生效问题

      一款配合ts、CSSModules的插件。 https://github.com/mrmckeb/typescript-plugin-css-modules#visual-studio-code 1、yarnadd-Dtypescript-plugin-css-modules 2、tsconfig.json { "compilerOptions":{ "plugins":[{"name":"typescript-plugin-css-modules"}] } } 复制 3、vscode的setting.json设置: "typescript.tsserver.pluginPaths":["typescript-plugin-css-modules"]复制 如果不生效,可以重启vscode(可能是缓存导致) 

  • 【转】如何统计网站(如个人博客)访问量

    转自:如何统计博客园的个人博客访问量 使用过新浪博客的人都知道,新浪博客的首页有访问量统计功能,迁移到博客园之后发现博客园却没有这项功能,所幸博客园在后台管理的设置选项中有一个公告栏和设置页首页脚代码功能,使用起来非常灵活和方便。借此我们可以通过网络上提过的其他强大的插件来为获取更加详细的访问统计信息(博客园这难道是借鉴了传统unix中“一个软件工具只做好一件事”的设计原则吗~~^_^),本教程所使用的方法对于其他提供类似公告栏机制的任何网络页面都适用,而不仅仅限于博客园,以下为详细步骤: 1.如果你想在博客园的首页显示你的访问数据,可以采用以下方法:   1)flagcounter.com:进入这个网站,然后根据首页的自定义界面选择你想要的显示模式,如下图所示:   选择自己喜欢的显示风格后,点击“getyourflagcounter"按钮,会生成两组代码:   这里我们选择html格式的代码,复制后粘贴到博客园后台管理的公告栏即可:   此时刷新你的博客主页就可以在公告栏看到你的统计信息了。以下是我的统计页面(黑底黄字的统计框来源于下面要介绍的statcounter):   注

  • 深度优先排序(数字全排列)

    输入一个整数n(n<10),输出1-n的全排列 1importjava.util.Scanner; 2publicclassOne{ 3//数组a(模拟放数字牌的盒子)用于存放排序数字,数组book[i]用于标记牌i是否已经放入数组a 4publicstaticinta[]=newint[10],book[]=newint[10],n; 5//函数f()用于输出所有可能情况的排列。 6publicstaticvoidf(intx){//x为第几个盒子 7if(x==n+1){//当每次放玩牌的时候就把当次的排序输出 8for(intj=1;j<=n;j++){ 9System.out.print(a[j]+""); 10} 11System.out.println(""); 12} 13for(inti=1;i<=n;i++){ 14if(book[i]==0){//如果牌i还在手上 15a[x]=i;//把牌i放入当前的盒子 16book[i]=1;//标记牌i已经放入盒子(不在手上了) 17f(x+1);//走到下一个盒子,继续排列 18book[i]=0;/

  • 5、Hadoop 2.6.5 环境搭建

    下载 地址:http://archive.apache.org/dist/hadoop/common/ sudowgethttp://archive.apache.org/dist/hadoop/common/hadoop-2.6.5/hadoop-2.6.5.tar.gz 复制 准备 官网文档:http://hadoop.apache.org/docs/r2.6.5/hadoop-project-dist/hadoop-common/SingleCluster.html sudoapt-getupdate sudoapt-getinstallopenjdk-8-jdk sudoapt-getinstall sudoapt-getinstallssh sudoapt-getinstallrsync sudoapt-getremoveopenjdk* 复制 /usr/lib/jvm/java-11-openjdk-amd64/bin/java http://archive.apache.org/dist/hadoop/common/hadoop-2.6.5/hadoop-2.6.5.t

  • elasticsearch 学习

    es是什么? es是基于ApacheLucene的开源分布式(全文)搜索引擎,,提供简单的RESTfulAPI来隐藏Lucene的复杂性。 1.分布式的实时存储,每个字段都被索引可被搜索 2.分布式实时分析搜索引擎 3.可以扩展到成千台服务器,处理pb级结构化或非结构化数据   es下载和安装 javaforwindows es对于javajdk的版本有需求,必须是java1.8及以上版本。 安装步骤参考:https://www.cnblogs.com/Neeo/articles/10368280.html esforwindows es开箱即用,也就是解压即可使用,安装参考https://www.cnblogs.com/Neeo/articles/10371306.html kibanaforwindows Kibana是一个为ElasticSearch提供的数据分析的Web接口。可使用它对日志进行高效的搜索、可视化、分析等各种操作。 安装参考:https://www.cnblogs.com/Neeo/articles/10371213.html es的快速上手 关系型数剧

  • .lib .dll 区别介绍、使用(dll的两种引入方式)

    .lib.dll文件都是程序可直接引用的文件,前者就是所谓的库文件,后者是动态链接库(DynamicLinkLibrary)也是一个库文件。而.pdb则可以理解为符号表文件。DLL(DynamicLinkLibrary)文件为动态链接库文件,又称为“应用程序扩展”,是一种软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即dll文件,放置于系统中。 关于lib和dll的区别 (1)lib是编译时用到的,dll是运行时用到的。如果要完成源代码的编译,只需要lib;如果要使动态链接的程序运行起来,只需要dll。(2)如果有dll文件,那么lib一般是一些索引信息,记录了dll中函数的入口和位置,dll中是函数的具体内容;如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。使用静态编译的lib文件,在运行程序时不需要再挂动态库,缺点是导致应用程序比较大,而且失去了动态库的灵活性,发布新版本时要发布新的应用程序才行。(3)动态链接的情况下,有两个文件:一个是LIB文件,一个是DLL文件。LIB包含被DLL导出

  • SpringBoot使用ServletFileUpload上传文件时servletFileUpload.parseRequest(request)为空

    1.问题描述 1.1SpringBoot使用ServletFileUpload上传文件时List<FileItem>items=servletFileUpload.parseRequest(request)为空 //获取ServletFileUpload ServletFileUploadservletFileUpload=getServletFileUpload(); List<FileItem>items=servletFileUpload.parseRequest(request);复制 /** *获取ServletFileUpload */ privateServletFileUploadgetServletFileUpload(){ //设置缓冲区大小,先读到内存里在从内存写 DiskFileItemFactoryfactory=newDiskFileItemFactory(); factory.setSizeThreshold(1024); Filefile=newFile(uploadPath); //如果文件夹不存在则创建 if(!file.ex

  • camunda日常操作

    camunda多数据源,即业务数据库与camunda数据库分开 业务数据库正常配置在application.yml中。添加camunda配置类 packagecom.example.workflow; importorg.camunda.bpm.engine.impl.cfg.StandaloneProcessEngineConfiguration; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.context.annotation.Configuration; @Configuration publicclassMyCamundaProcessEngineConfigurationextendsStandaloneProcessEngineConfiguration{ @Value("${camunda.db.url}") privateStringjdbcUrl; @Value("${camunda.db.username}") privateStrin

  • linux 复合页( Compound Page )的介绍

    1、复合页的定义:   复合页(CompoundPage)就是将物理上连续的两个或多个页看成一个独立的大页,它可以用来创建hugetlbfs中使用的大页(hugepage), 也可以用来创建透明大页(transparenthugepage)子系统。但是它不能用在页缓存(pagecache)中,这是因为页缓存中管理的都是单个页。   2、复合页的分配及标记:   当__alloc_pages分配标志gfp_flags指定了__GFP_COMP,那么内核必须将这些页组合成复合页compoundpage。复合页的尺寸要远大于当前分页系统支持的页面大小。并且一定是2^order*PAGE_SIZE大小。复合页主要用在HugeTLB相关的代码。复合页的引入是因为随着计算机物理内存容量不断增大,4G以上几乎成了标配,几十G的内存也很常见,而操作系统仍然使用4KB大小页面的基本单位,显得有些滞后。当采用4KB大小的页面时,想像一下当应用程序分配2MB内存,并进行访问时,共有512个页面,操作系统会经历512次TLBmiss和512次缺页中断后,才可以把这2M地址空间全部映射到物理

  • 递归

      刚开始学习递归的时候有点懵,老是不理解结束条件,后来看到一篇文章才开始掌握递归的思想 https://mp.weixin.qq.com/s/mJ_jZZoak7uhItNgnfmZvQ

  • Command line is too long. Shorten command line for xxxxxxxxxxxxxxxxxx

    .idea/workspace.xml在中<componentname="PropertiesComponent">xxxx</component>添加<propertyname="dynamic.classpath"value="true"/>

  • (6)解构赋值的用途

    解构赋值的用途1.交换变量的值 vara=100; varb=200; vart; t=a; a=b; b=t; //解构赋值的写法完成【ES6交换变量的值】 varx=100; vary=200; [x,y]=[y,x]; console.log(x); console.log(y); 优点1:直观 优点2:一一对应 优点3:节省内存空间(不用多申请变量)复制 2.从函数返回多个值 //从函数返回多个值_返回一个数组 functionfun(){ return[1,2,3]; }; console.log(fun()[1]);//2 functionfun(){ return[1,2,3]; }; var[x,y,z]=fun(); console.log(x);//1 console.log(y);//2 console.log(z);//3 //从函数返回多个值_返回一个对象(取值方便可读性更好) functionfun(){ return{ id:'007', name:'jewave', age:26 }; }; var{id,name,age}=fun();

相关推荐

推荐阅读