自定义注解加 AOP 实现服务接口鉴权以及内部认证
注解
何谓注解?
在Java中,注解(Annotation)是一种特殊的语法,用@符号开头,是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
拿熟悉 的@Override 注解来看。
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
JDK 内置了很多注解(比如 @Override、@Deprecated),其他框架如 Spring 也内置了不少注解,我们也可以自定义注解。
注解的作用
注解的主要作用是提供元数据,具体可以用于以下几个方面:
- 编译时检查:如@Override可以帮助编译器检查该方法是否正确重写了父类的方法。
- 代码生成:如@Entity可以告诉框架生成对应的数据库表。
- 运行时处理:如@Deprecated可以在运行时提醒开发者某个方法或类已经不建议使用。
注解的解析方法有哪几种?
注解只有被解析之后才会生效,常见的解析方法有两种:
- 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
- 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的。也是我们自定义注解中使用最多的。
如何自定义注解?
自定义注解主要包括以下几个步骤:
- 1.定义注解:使用@interface关键字定义注解。
- 2.注解元素:在注解中定义元素,就像在接口中定义方法。
- 3.元注解:使用元注解(如@Retention、@Target等)来描述注解的行为。
1. 定义注解
可以使用@interface关键字来定义一个注解。下面是一个简单的例子:
public @interface MyAnnotation {
String value();
int number() default 0;
}
在上面的例子中, MyAnnotation 注解有两个元素: value 和 number 。其中, number 有一个默认值 0 。
2. 元注解
元注解是注解的注解,用来描述注解本身的行为。常见的元注解有:
- @Retention:指明注解的保留策略。
- @Target:指明注解的使用目标。
@Retention
@Retention指定了注解的生命周期,它有三个取值:
- RetentionPolicy.SOURCE:注解只在源代码中存在,编译后就不存在了。
- RetentionPolicy.CLASS:注解在编译后会存在于.class文件中,但在运行时不会存在。
- RetentionPolicy.RUNTIME:注解在运行时依然存在,可以通过反射读取。
@Target
@Target指定了注解可以使用的地方,如类、方法、字段等。常见的取值有:
- ElementType.TYPE:用于类、接口、枚举、注解类型。
- ElementType.FIELD:用于字段或属性。
- ElementType.METHOD:用于方法。
- ElementType.PARAMETER:用于参数。
- ElementType.CONSTRUCTOR:用于构造函数。
- ElementType.LOCAL_VARIABLE:用于局部变量。
3. 完整的自定义注解示例
下面是一个包含@Retention和@Target元注解的完整自定义注解示例:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
int number() default 0;
}
4. 使用自定义注解
定义完注解后,可以在代码中使用它:
public class Test {@MyAnnotation(value = "Test method", number = 42)
public void testMethod() {
// 方法的具体实现
}
}
5. 通过反射读取注解
可以使用反射机制读取并处理注解(本项目中的 AOP 切面原理就是如此):
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
Method method = Test.class.getMethod("testMethod");if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println("Value: " + annotation.value());
System.out.println("Number: " + annotation.number());
}
}
}
Spring 特性
八股中的 Spring 的两大特性熟的不能再熟了吧?
●IoC(控制反转)
●AOP 面向切面编程
面向切面编程 AOP
这里我们重点介绍下 AOP,因为项目中使用到。
面向切面编程是一种编程范式,它允许在不改变业务逻辑代码的情况下,将横切关注点(如日志记录、事务管理、安全检查等)模块化。AOP通过定义切面(Aspect)和切点(Pointcut)来实现这一点。
Spring AOP 提供了多种方式来定义和使用切面,包括:
●注解:使用@Aspect和相关注解(如@Before、@After、@Around等)来定义切面和切点。
●XML配置:在Spring配置文件中定义切面和切点(较少使用,现代开发中更常用注解)。
微服务架构
微服务简而言之就是单个独立的服务,可以独立开发、部署和维护。而微服务架构是指的多个微服务聚合起来的系统,这个系统涵盖多个微服务,服务与服务之间的通讯、服务监控、服务熔断降级、服务注册、分布式配置、分布式事务等各种解决方案聚合而成的架构体系。
微服务架构有如下优点:
- 提高开发效率:团队可以并行开发不同的微服务,减少了开发和发布的时间。
- 增强可维护性:小而专注的代码库更易于理解和维护,降低了技术债务。
- 灵活的技术选型:不同的微服务可以根据需要使用最合适的技术栈,而无需在整个系统中保持一致。
- 持续交付和部署:微服务架构支持持续集成和持续交付(CI/CD),使得新功能和修复能够快速上线。
- 更好的故障隔离:一个微服务的故障不会影响其他微服务的正常运行,从而提高系统的可靠性。
- 按需扩展:可以独立地扩展需要高负载处理的微服务,优化资源使用和成本。
鉴权基础
鉴权顾名思义就是需要进行权限认证和授权控制,你写好的系统不希望谁都可以访问吧?你写的牛逼的接口也不希望哪个人都可以来蹭一下访问吧?那就需要认证和授权。
专业做这块的有 Spring Security 和 Shiro 这两哥们,当然还有一些其他的框架也是可以做的,但无非核心都在做两件事:
- 认证
- 授权
认证,说白了就是登录,传统 web 登录是通过用户名和密码用 Cookie+Session 的方式,这种依赖于服务器本地内存,微服务中,显然不合适。
常见的鉴权方式有以下几种:
用户名和密码
是最传统和常见的鉴权方式,用户通过输入预先设置的用户名和密码进行登录,需要注意密码的存储和传输安全,如使用加盐哈希存储和HTTPS 传输。
多因素认证(MFA)
这是一种增强安全性的方法,通过要求多种不同类型的验证因素来确认用户身份,常见的因素包括:知识因子(密码)、拥有因子(手机验证码)、生物因子(指纹、面部识别)。
OAuth(开放授权)
这是一种一种授权协议,允许第三方应用以有限的权限访问用户资源,而无需暴露用户的凭证。常用于社交登录和API访问控制。
JWT(JSON Web Token)
一种基于 JSON 的开放标准(RFC 7519),用于在各方之间传递声明。JWT包含用户信息和签名,可用于鉴权和授权。我们这次也是采用的这种方式进行的鉴权。
项目实战中如何做鉴权认证
项目中的架构
微服务架构中,通常有多个独立服务组成,这些服务可能部署在不同的服务器或数据中心, 鉴权机制需要在分布式环境中有效运作,确保各个服务能安全通信,且需要有统一认证中心,我们先来看一张 PmHub 的架构图:
PmHub 中有一个单独的微服务来做认证,也就是认证服务 pmhub-auth,对于 PmHub 而言,请求一般分为 2 种:
- 通过 API 网关的请求
- 微服务内部请求
对于这两种请求,都需要进行鉴权,但方式是不一样的
PmHub 中如何做认证
微服务中的认证最多的方式是通过 JWT 令牌的方式,但 JWT 实际上是无状态的,也就是没法确定登录的用户啥时候过期,所以大部分情况下会需要结合 Redis 来设置状态。

将生成的 JWT 字符串在 Redis 上也保存一份,并设置过期时间,判断用户是否登录时,需要先去 Redis 上查看 JWT 字符串是否存在,存在的话再对 JWT 字符串做解析操作,如果能成功解析,就没问题,如果不能成功解析,就说明令牌不合法。

PmHub 也是采取的这个逻辑,这是一个简单的流程图:
在认证服务中,检查用户名密码的正确性,正确的话就生成 JWT 字符串,同时再把数据存入到 Redis 上,然后返回 token 信息。登录请求先经过网关,网关再转发到认证服务,下面是一个具体的流程:
放在系统层面上流程用例比较复杂,为了方便大家理解,可以看如下图:
可以看到用户登录逻辑其实是涉及到多服务交互的,大家可以对着代码看流程图,理解起来会更深入一些。
PmHub 中如何做鉴权
鉴权(或者说是授权)是请求到达每个微服务后,需要对请求进行权限判定,看是否有权限访问,通常不会放在网关中来做。还是在微服务中自己来做。
我们说过,在 PmHub 中,请求主要分为 2 种,外部请求和内部请求,下面是不同的授权思路。
外部请求
PmHub 的做法是:请求到达网关后,通过微服务的自定义请求头拦截器(可以放在公共包下面,每个服务都可以引用),配合自定义注解和 AOP,拦截请求头,获取用户和权限信息,然后进行比对,有权限则放行,没权限则抛出异常。
内部请求
对于内部的请求来说,正常是不需要鉴权的,内部请求可以直接处理。问题是如果使用了 OpenFeign,数据都是通过接口暴露出去的,不鉴权的话,又会担心从外部来的请求调用这个接口,对于这个问题,我们也可以自定义注解+AOP,然后在内部请求调用的时候,额外加一个头字段加以区分。
我采用的是自定义内部请求注解,然后 AOP 控制拦截。
内部请求注解
/*** 内部认证注解* * @author canghe*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth
{
/*** 是否校验用户信息*/
boolean isUser() default false;
}
AOP 的切面控制请求是否携带有内部请求的标识:
内部请求切面
/*** 内部服务调用验证处理** @author canghe*/
@Aspect
@Component
public class InnerAuthAspect implements Ordered {@Around("@annotation(innerAuth)")
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable {
String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
// 内部请求验证
if (!StringUtils.equals(SecurityConstants.INNER, source)) {
throw new InnerAuthException("没有内部访问权限,不允许访问");
}String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
// 用户信息验证
if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))) {
throw new InnerAuthException("没有设置用户信息,不允许访问 ");
}
return point.proceed();
}
因为使用的是 OpenFeign,请求通过 OpenFeign 调用也需要鉴权,所以我实现了 feign.RequestInterceptor 接口来定义一个 OpenFeign 的请求拦截器,在拦截器中,统一为 OpenFeign 请求设置请求头信息。
/*** feign 请求拦截器** @author canghe*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
if (StringUtils.isNotNull(httpServletRequest)) {
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失
String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
if (StringUtils.isNotEmpty(userId)) {
requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);
}
String userKey = headers.get(SecurityConstants.USER_KEY);
if (StringUtils.isNotEmpty(userKey)) {
requestTemplate.header(SecurityConstants.USER_KEY, userKey);
}
String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(userName)) {
requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName);
}
String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication)) {
requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
}// 配置客户端IP
requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());
}
}
以上,是 PmHub 中的认证鉴权逻辑。
相关文章:
自定义注解加 AOP 实现服务接口鉴权以及内部认证
注解 何谓注解? 在Java中,注解(Annotation)是一种特殊的语法,用符号开头,是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信…...
《软件工程概论》作业一:新冠疫情下软件产品设计(小区电梯实体按钮的软件替代方案)
课程说明:《软件工程概论》为浙江科技学院2018级软件工程专业在大二下学期开设的必修课。课程使用《软件工程导论(第6版)》(张海藩等编著,清华大学出版社)作为教材。以《软件设计文档国家标准GBT8567-2006》…...
基于Ernie-Bot打造语音对话功能
大模型场景实战培训,提示词效果调优,大模型应用定制开发,点击咨询 咨询热线:400-920-8999转2 GPT-4的语音对话功能前段时间在网上火了一把,许多人被其强大的自然语言处理能力和流畅的语音交互所吸引。现在,…...
动手学深度学习(李沐)PyTorch 第 3 章 线性神经网络
3.1 线性回归 线性回归是对n维输入的加权,外加偏差 线性回归可以看作是单层神经网络 回归问题中最常用的损失函数是平方误差函数。 平方误差可以定义为以下公式: 常数1/2不会带来本质的差别,但这样在形式上稍微简单一些 (因为当…...
ROS理论与实践学习笔记——2 ROS通信机制之服务通信
服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A,用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输…...
技术成神之路:设计模式(十八)适配器模式
介绍 适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的类可以协同工作,通过将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。 1.定义 适配…...
图神经网络:处理复杂关系结构与图分类任务的强大工具
创作不易,您的打赏、关注、点赞、收藏和转发是我坚持下去的动力! 图神经网络(Graph Neural Network, GNN)是针对图数据的一类神经网络模型。图数据具有节点(节点代表实体)和边(边代表节点之间的…...
LeetCode: 1971. 寻找图中是否存在路径
寻找图中是否存在路径 原题 有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点…...
mysql 查询表所有数据,分页的语句
在 MySQL 中,若要从表中查询所有数据并实现分页,你可以使用 SELECT 语句结合 LIMIT 和 OFFSET 子句。LIMIT 用于指定返回的记录数,而 OFFSET 则用于指定从哪一条记录开始返回(即跳过的记录数)。 以下是一个基本的分页…...
TI DSP TMS320F280025 Note13:CPUtimer定时器原理分析与使用
TMS320F280025 CPUtimer定时器原理分析与使用 ` 文章目录 TMS320F280025 CPUtimer定时器原理分析与使用框图分析定时器中断定时器使用CPUtimers.cCPUtimers.h框图分析 定时器框图如图所示 定时器有一个预分频模块和一个定时/计数模块, 其中预分频模块包括一个 16 位的定时器分…...
Australis 相機率定軟體說明
概要 課堂中使用Australis這套軟體,順帶記錄操作過程 內容以老師口述及我測試的經過 照片為老師課堂提供之 說明 執行 Step1. 匯入照片 注意!!如果是Mac的作業系統,將資料夾移到Windows上的時候,建議創一個新的資料…...
C++入门(有C语言基础)
string类 string类初始化的方式大概有以下几种: string str1;string str2 "hello str2";string str3("hello str3");string str4(5, B);string str5[3] {"Xiaomi", "BYD", "XPeng"};string str6 str5[2];str…...
第四届高性能计算与通信工程国际学术会议(HPCCE 2024)
目录 大会简介 主办单位,承办单位 征稿主题 会议议程 参会方式 大会官网:www.hpcce.net 大会简介 第四届高性能计算与通信工程国际学术会议(HPCCE 2024)将于2024年11月22-24日在苏州召开。HPCCE 2024将围绕“高性能计算与通信工…...
负载均衡架构解说
负载均衡架构是一种设计模式,用于在多个服务器之间分配网络或应用流量,以提高资源利用率、最大化吞吐量、减少响应时间,并确保高可用性。 负载均衡架构的关键组件和概念: 关键组件 1.负载均衡器(Load Balancer&…...
【异常数据检测】孤立森林算法异常数据检测算法(数据可视化 Matlab语言)
摘要 本文研究了基于孤立森林算法的异常数据检测方法,并在MATLAB中实现了该算法的可视化。孤立森林是一种无监督的异常检测算法,主要通过构建决策树来区分正常数据和异常数据。本文使用真实数据集,通过二维可视化展示了检测结果。实验结果表…...
MKV转MP4丨FFmpeg的简单命令使用——视频格式转换
MKV是一种视频封装格式,很好用,也是OBS的默认推荐录制格式,因为不会突然断电关机而导致整个视频录制文件丢失。 但是MKV无法直接导入PR中剪辑,最直接的方法是将MKV转换为MP4格式,最方便且安全无损的转换方法便是用FFmp…...
git使用“保姆级”教程4——版本回退及分支讲解
一、版本回退 1、历史回退(版本回退)——命令行git reset --hard 版本编号 注意:当前命令会让工作区的内容发生改变,可以理解成历史区(master分支)直接回到工作区比如:从版本4回到版本3,则工作区只会显示版本3的代码内容 1.1、指…...
spring cache,Spring data redis
本项目使用Redis存储缓存数据,如何通过Java去访问Redis? 常用的有Jedis和Lettuce两个访问redis的客户端类库 ,Jedis和Lettuce都是redis提供的。其中Lettuce的性能和并发性要好一些,Spring Boot 默认使用的是 Lettuce 作为 Redis …...
10.数据结构与算法-线性表的应用(线性表与有序表的合并)
线性表的合并 有序表的合并 顺序表 链表...
GAN|对抗| 生成器更新|判别器更新过程
如上图所示,生成对抗网络存在上述内容: 真实数据集;生成器;生成器损失函数;判别器;判别器损失函数;生成器、判别器更新(生成器和判别器就是小偷和警察的关系,他们共用的…...
day01——登录功能
逻辑: 前端将登录信息通过报文的形式,发送给后端。后端进行登陆验证 2.1 根据接受的用户名,查询数据表。 若不存在该用户的记录,返回用户不存在。 若用户存在,判断数据库中的密码和接收的是否一致,不一致则…...
Flutter中使用FFI的方式链接C/C++的so库(harmonyos)
Flutter中使用FFI的方式链接C/C库(harmonyos) FFI plugin创建和so的配置FFI插件对so库的使用 FFI plugin创建和so的配置 首先我们可以根据下面的链接生成FFI plugin插件:开发FFI plugin插件 然后在主项目中pubspec.yaml 添加插件的依赖路径&…...
【C++】二义性
在C中,二义性(ambiguity)通常指的是编译器无法确定使用哪个函数、变量或类成员的情况。这种不确定性通常是由于继承和多态特性导致的。下面是一些常见的产生二义性的场景以及如何解决它们的方法: 1. 多重继承中的二义性 当一个类…...
高并发内存池(五):ThreadCache、CentralCache和PageCache的内存回收机制、阶段性代码展示和释放内存过程的调试
目录 ThreadCache的内存回收机制 补充内容1 补充内容2 补充内容3 补充内容4 ListTooLong函数的实现 CentralCache的内存回收机制 MapObjectToSpan函数的实现 ReleaseListToSpans函数的实现 PageCache的内存回收机制 补充内容1 补充内容2 ReleaseSpanToPageCache函…...
STL之stackqueue篇(上)探索C++ STL中的Queue与Stack——构建数据处理的基础框架
文章目录 前言一、stack1.1 定义与基本概念1.2 底层容器1.3 成员函数1.4 使用示例1.5 注意事项1.6 应用场景 二、queue2.1 定义与基本概念2.2 底层容器2.3 成员函数2.4 使用示例2.5 注意事项2.6 应用场景 前言 本文旨在深入探讨C STL中的queue与stack容器,从它们的…...
代码随想录算法训练营Day13
110.平衡二叉树 力扣题目链接:. - 力扣(LeetCode) 后序迭代 class Solution {public boolean isBalanced(TreeNode root) {return getHeight(root)!-1;}public int getHeight(TreeNode root){if(rootnull){return 0;}int leftheightgetHei…...
基于STM32的智能门禁系统
目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 RFID数据采集与处理门禁控制实现显示与报警功能应用场景结论 1. 引言 智能门禁系统在现代安防中占据重要地位,通常用于控制进入和离开特定区域的权限。通过基于STM32微控制器…...
[EBPF] 实时捕获DM数据库是否存在SQL阻塞
1. 介绍 eBPF(extened Berkeley Packet Filter)是一种内核技术,它允许开发人员在不修改内核代码的情况下运行特定的功能。eBPF 的概念源自于 Berkeley Packet Filter(BPF),后者是由贝尔实验室开发的一种网…...
秋招内推--招联金融2025
【投递方式】 直接扫下方二维码,或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus,使用内推码 igcefb 投递) 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…...
Unity2022.3.x各个版本bug集合及推荐稳定版本
最近升级到Unity2022,发现以下问题,仅作参考 2022.3.0f1 - 2022.3.6f1 粒子渲染到RenderTexture闪屏 https://issuetracker.unity3d.com/issues/android-vulkan-visualisation-corruption-occurs-when-rendering-particles-to-render-texture 2022.3.…...
网站开发外贸/怎么做优化关键词
2019独角兽企业重金招聘Python工程师标准>>> Laravel使用PHP的一个扩展API–Carbon来处理时间。它提供了很多日期操作方法,其中 diffForHumans() 可以把日期转换成 “1 hours age”、“4 years age” 这样的格式,同时,它还支持本地…...
可以做公司宣传的网站有哪些/互联网营销模式有哪些
1 概述 在平时开发中,git可以说是我们最不陌生的工具了。而且在提交线上代码或者是将自己功能分支上的代码给cherry-pick到预发分支上时,如果只提交了一个点那么直接cherry-pick就可以了,但是提交了很多点时,一个一个的cherry-pic…...
怎样把有用网站做图标放在桌面/软文推广营销平台
2019独角兽企业重金招聘Python工程师标准>>> 关于区块链是什么的话题,估计现在已经烂大街了。但是实际上, 那些我们认为已经非常普通的概念,却往往别有洞天。 1.从社会学角度上来讲: 区块链的概念来自凯文.凯利《失控》…...
国外做内容网站/优化网络推广外包
大数据时代,什么最贵? 十年前,葛大爷曾说过,“21世纪什么最贵?”——“人才”,深以为然。只是,十年后的今天,大数据时代也带来了身价不断翻番的各种数据。由于急速拓展的网络带宽以及各种穿戴设备所带来…...
易企秀怎么做招聘网站超链接/虞城seo代理地址
一、设计基本思路: 首先要根据塑件的基本要求和塑料的工艺性能,认真分析塑件的工艺性,然后确定成型方法及成型工艺,选择合适的塑料注射成型机,接下来就可以进行塑料模具的设计。 塑料模具 二、在设计塑料模具时需注意…...
c2c网站的类型/百度推广官方网站
skyline是一款不错的三维编辑浏览软件,官方提供的是英文版,目前还没有汉化包,为了使用方便,我们需要汉化一些简单的对话框,本文介绍如何汉化skyline右键菜单。 首先打开skyline的安装目录,在TerraExplorer …...